forked from I2P_Developers/i2p.i2p
Blockfile: Move from i2p.jar to addressbook.jar
http://zzz.i2p/topics/2274
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -19,8 +19,10 @@ import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* A Dummy naming service that can only handle base64 and b32 destinations.
|
||||
*
|
||||
* @since public since 0.9.31
|
||||
*/
|
||||
class DummyNamingService extends NamingService {
|
||||
public class DummyNamingService extends NamingService {
|
||||
|
||||
protected static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
|
||||
public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32";
|
||||
|
@ -37,7 +37,8 @@ public abstract class NamingService {
|
||||
|
||||
/** what classname should be used as the naming service impl? */
|
||||
public static final String PROP_IMPL = "i2p.naming.impl";
|
||||
private static final String DEFAULT_IMPL = "net.i2p.client.naming.BlockfileNamingService";
|
||||
private static final String DEFAULT_IMPL = "net.i2p.router.naming.BlockfileNamingService";
|
||||
private static final String OLD_DEFAULT_IMPL = "net.i2p.client.naming.BlockfileNamingService";
|
||||
private static final String BACKUP_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
|
||||
|
||||
/**
|
||||
@ -751,7 +752,10 @@ public abstract class NamingService {
|
||||
*/
|
||||
public static final synchronized NamingService createInstance(I2PAppContext context) {
|
||||
NamingService instance = null;
|
||||
String dflt = context.isRouterContext() ? DEFAULT_IMPL : BACKUP_IMPL;
|
||||
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
|
||||
if (impl.equals(OLD_DEFAULT_IMPL))
|
||||
impl = dflt;
|
||||
try {
|
||||
Class<?> cls = Class.forName(impl);
|
||||
Constructor<?> con = cls.getConstructor(I2PAppContext.class);
|
||||
|
@ -268,9 +268,9 @@ public class SingleFileNamingService extends NamingService {
|
||||
* Does not write a newline.
|
||||
*
|
||||
* @param options non-null
|
||||
* @since 0.9.26, package private since 0.9.30
|
||||
* @since 0.9.26, package private since 0.9.30, public since 0.9.31
|
||||
*/
|
||||
static void writeOptions(Properties options, Writer out) throws IOException {
|
||||
public static void writeOptions(Properties options, Writer out) throws IOException {
|
||||
boolean started = false;
|
||||
for (Map.Entry<Object, Object> e : options.entrySet()) {
|
||||
String k = (String) e.getKey();
|
||||
|
@ -21,7 +21,6 @@ public class CommandLine {
|
||||
protected static final List<String> CLASSES = Arrays.asList(new String[] {
|
||||
"freenet.support.CPUInformation.CPUID",
|
||||
"net.i2p.CoreVersion",
|
||||
"net.i2p.client.naming.BlockfileNamingService",
|
||||
"net.i2p.crypto.CertUtil",
|
||||
"net.i2p.crypto.CryptoCheck",
|
||||
"net.i2p.crypto.SU3File",
|
||||
|
@ -1,7 +0,0 @@
|
||||
package net.metanotion;
|
||||
|
||||
/**
|
||||
* Exists only to enable package.html to be included in javadoc.
|
||||
* http://java.sun.com/j2se/javadoc/faq/index.html#packagewithoutjavafiles
|
||||
*/
|
||||
abstract class Dummy {}
|
@ -1,47 +0,0 @@
|
||||
Version 0.1.1 from http://www.metanotion.net/software/sandbox/block.html
|
||||
|
||||
License: See any source file.
|
||||
|
||||
This is a partial list of changes for I2P. For details, see history.txt
|
||||
and the source control logs.
|
||||
|
||||
- BSkipList has an option to not keep everything in memory.
|
||||
When this option is enabled, we use the new IBSkipSpan instead of
|
||||
BSkipSpan. IBSkipSpan has the following changes:
|
||||
* Only the first key in the span, and no values, are stored in memory
|
||||
* put() and remove() read the span keys and values in from disk first
|
||||
* flush() nulls out the keys and values after flushing to disk
|
||||
* get() does a linear search through the keys on disk
|
||||
|
||||
- The metaIndex is stored in-memory. All "user" skiplists are not
|
||||
stored in-memory.
|
||||
|
||||
- Default span size changed from 127 to 16
|
||||
|
||||
- Use I2P random source
|
||||
|
||||
- Return the previous SkipList if still open from a call to getIndex()
|
||||
|
||||
- Add a closeIndex() method
|
||||
|
||||
- Commented out some System.out.println()
|
||||
|
||||
- Convert Errors without message or cause to RuntimeExceptions with a message and cause
|
||||
|
||||
- Add unique on-disk headers to each page type for robustness;
|
||||
change blockfile magic number as these headers are required
|
||||
|
||||
- Automatically repair some types of corruption;
|
||||
report some other corruption types without failing
|
||||
|
||||
- Support read-only databases
|
||||
|
||||
- Lots and lots of bug fixes
|
||||
|
||||
|
||||
TODO:
|
||||
|
||||
- More catching and repair of corruption
|
||||
|
||||
- Change PAGESIZE from default 1024 to 4096? No, wastes too much disk.
|
||||
|
@ -1,170 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class RAIFile implements RandomAccessInterface, DataInput, DataOutput {
|
||||
private final File f;
|
||||
private final RandomAccessFile delegate;
|
||||
private final boolean r, w;
|
||||
|
||||
public RAIFile(RandomAccessFile file) throws FileNotFoundException {
|
||||
this.f = null;
|
||||
this.delegate = file;
|
||||
this.r = true;
|
||||
// fake, we don't really know
|
||||
this.w = true;
|
||||
}
|
||||
|
||||
/** @param read must be true */
|
||||
public RAIFile(File file, boolean read, boolean write) throws FileNotFoundException {
|
||||
this.f = file;
|
||||
this.r = read;
|
||||
this.w = write;
|
||||
String mode = "";
|
||||
if(this.r) { mode += "r"; }
|
||||
if(this.w) { mode += "w"; }
|
||||
this.delegate = new RandomAccessFile(file, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P is the file writable?
|
||||
* Only valid if the File constructor was used, not the RAF constructor
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public boolean canWrite() {
|
||||
return this.w;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.8
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.f != null)
|
||||
return this.f.getAbsolutePath();
|
||||
return this.delegate.toString();
|
||||
}
|
||||
|
||||
public long getFilePointer() throws IOException { return delegate.getFilePointer(); }
|
||||
public long length() throws IOException { return delegate.length(); }
|
||||
public int read() throws IOException { return delegate.read(); }
|
||||
public int read(byte[] b) throws IOException { return delegate.read(b); }
|
||||
public int read(byte[] b, int off, int len) throws IOException { return delegate.read(b,off,len); }
|
||||
public void seek(long pos) throws IOException { delegate.seek(pos); }
|
||||
public void setLength(long newLength) throws IOException { delegate.setLength(newLength); }
|
||||
|
||||
// Closeable Methods
|
||||
// TODO May need to change.
|
||||
public void close() throws IOException { delegate.close(); }
|
||||
|
||||
// DataInput Methods
|
||||
public boolean readBoolean() throws IOException { return delegate.readBoolean(); }
|
||||
public byte readByte() throws IOException { return delegate.readByte(); }
|
||||
public char readChar() throws IOException { return delegate.readChar(); }
|
||||
public double readDouble() throws IOException { return delegate.readDouble(); }
|
||||
public float readFloat() throws IOException { return delegate.readFloat(); }
|
||||
public void readFully(byte[] b) throws IOException { delegate.readFully(b); }
|
||||
public void readFully(byte[] b, int off, int len) throws IOException { delegate.readFully(b,off,len); }
|
||||
public int readInt() throws IOException { return delegate.readInt(); }
|
||||
public String readLine() throws IOException { return delegate.readLine(); }
|
||||
public long readLong() throws IOException { return delegate.readLong(); }
|
||||
public short readShort() throws IOException { return delegate.readShort(); }
|
||||
public int readUnsignedByte() throws IOException { return delegate.readUnsignedByte(); }
|
||||
public int readUnsignedShort() throws IOException { return delegate.readUnsignedShort(); }
|
||||
|
||||
/**
|
||||
* I2P
|
||||
* @throws IOException if the read value is negative
|
||||
*/
|
||||
public int readUnsignedInt() throws IOException {
|
||||
int rv = readInt();
|
||||
if (rv < 0)
|
||||
throw new IOException("Negative value for unsigned int: " + rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** Read a UTF encoded string
|
||||
I would delegate here. But Java's read/writeUTF combo suck.
|
||||
A signed 2 byte length is not enough.
|
||||
This reads a 4 byte length.
|
||||
The upper byte MUST be zero, if its not, then its not this method and has used an
|
||||
extensible length encoding.
|
||||
This is followed by the bytes of the UTF encoded string, as
|
||||
returned by String.getBytes("UTF-8");
|
||||
*/
|
||||
public String readUTF() throws IOException {
|
||||
int len = delegate.readInt();
|
||||
if((len < 0) || (len >= 16777216)) { throw new IOException("Bad Length Encoding"); }
|
||||
byte[] bytes = new byte[len];
|
||||
int l = delegate.read(bytes);
|
||||
if(l==-1) { throw new IOException("EOF while reading String"); }
|
||||
String s = new String(bytes, "UTF-8");
|
||||
return s;
|
||||
}
|
||||
|
||||
public int skipBytes(int n) throws IOException { return delegate.skipBytes(n); }
|
||||
|
||||
// DataOutput Methods
|
||||
public void write(int b) throws IOException { delegate.write(b); }
|
||||
public void write(byte[] b) throws IOException { delegate.write(b); }
|
||||
public void write(byte[] b, int off, int len) throws IOException { delegate.write(b,off,len); }
|
||||
public void writeBoolean(boolean v) throws IOException { delegate.writeBoolean(v); }
|
||||
public void writeByte(int v) throws IOException { delegate.writeByte(v); }
|
||||
public void writeShort(int v) throws IOException { delegate.writeShort(v); }
|
||||
public void writeChar(int v) throws IOException { delegate.writeChar(v); }
|
||||
public void writeInt(int v) throws IOException { delegate.writeInt(v); }
|
||||
public void writeLong(long v) throws IOException { delegate.writeLong(v); }
|
||||
public void writeFloat(float v) throws IOException { delegate.writeFloat(v); }
|
||||
public void writeDouble(double v) throws IOException { delegate.writeDouble(v); }
|
||||
public void writeBytes(String s) throws IOException { delegate.writeBytes(s); }
|
||||
public void writeChars(String s) throws IOException { delegate.writeChars(s); }
|
||||
|
||||
/** Write a UTF encoded string
|
||||
I would delegate here. But Java's read/writeUTF combo suck.
|
||||
A signed 2 byte length is not enough.
|
||||
This writes a 4 byte length.
|
||||
The upper byte MUST be zero, if its not, then its not this method and has used an
|
||||
extensible length encoding.
|
||||
This is followed by the bytes of the UTF encoded string, as
|
||||
returned by String.getBytes("UTF-8");
|
||||
*/
|
||||
public void writeUTF(String str) throws IOException {
|
||||
byte[] string = str.getBytes("UTF-8");
|
||||
if(string.length >= 16777216) { throw new IOException("String to long for encoding type"); }
|
||||
delegate.writeInt(string.length);
|
||||
delegate.write(string);
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface RandomAccessInterface extends Closeable {
|
||||
public long getFilePointer() throws IOException;
|
||||
public long length() throws IOException;
|
||||
public int read() throws IOException;
|
||||
public int read(byte[] b) throws IOException;
|
||||
public int read(byte[] b, int off, int len) throws IOException;
|
||||
public void seek(long pos) throws IOException;
|
||||
public void setLength(long newLength) throws IOException;
|
||||
|
||||
/**
|
||||
* I2P is the file writable?
|
||||
* Only valid if the File constructor was used, not the RAF constructor
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public boolean canWrite();
|
||||
|
||||
// Closeable Methods
|
||||
public void close() throws IOException;
|
||||
|
||||
// DataInput Methods
|
||||
public boolean readBoolean() throws IOException;
|
||||
public byte readByte() throws IOException;
|
||||
public char readChar() throws IOException;
|
||||
public double readDouble() throws IOException;
|
||||
public float readFloat() throws IOException;
|
||||
public void readFully(byte[] b) throws IOException;
|
||||
public void readFully(byte[] b, int off, int len) throws IOException;
|
||||
public int readInt() throws IOException;
|
||||
public String readLine() throws IOException;
|
||||
public long readLong() throws IOException;
|
||||
public short readShort() throws IOException;
|
||||
public int readUnsignedByte() throws IOException;
|
||||
public int readUnsignedShort() throws IOException;
|
||||
// I2P
|
||||
public int readUnsignedInt() throws IOException;
|
||||
public String readUTF() throws IOException;
|
||||
public int skipBytes(int n) throws IOException;
|
||||
|
||||
// DataOutput Methods
|
||||
public void write(int b) throws IOException;
|
||||
public void write(byte[] b) throws IOException;
|
||||
public void write(byte[] b, int off, int len) throws IOException;
|
||||
public void writeBoolean(boolean v) throws IOException;
|
||||
public void writeByte(int v) throws IOException;
|
||||
public void writeShort(int v) throws IOException;
|
||||
public void writeChar(int v) throws IOException;
|
||||
public void writeInt(int v) throws IOException;
|
||||
public void writeLong(long v) throws IOException;
|
||||
public void writeFloat(float v) throws IOException;
|
||||
public void writeDouble(double v) throws IOException;
|
||||
public void writeBytes(String s) throws IOException;
|
||||
public void writeChars(String s) throws IOException;
|
||||
public void writeUTF(String str) throws IOException;
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io;
|
||||
|
||||
public interface Serializer<T> {
|
||||
public byte[] getBytes(T o);
|
||||
public T construct(byte[] b);
|
||||
}
|
@ -1,671 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import net.metanotion.io.RAIFile;
|
||||
import net.metanotion.io.RandomAccessInterface;
|
||||
import net.metanotion.io.Serializer;
|
||||
import net.metanotion.io.data.IdentityBytes;
|
||||
import net.metanotion.io.data.IntBytes;
|
||||
import net.metanotion.io.data.StringBytes;
|
||||
import net.metanotion.io.data.UTF8StringBytes;
|
||||
import net.metanotion.io.block.index.BSkipList;
|
||||
import net.metanotion.io.block.index.BSkipSpan;
|
||||
import net.metanotion.util.skiplist.SkipIterator;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* On-disk format:
|
||||
* Magic number (6 bytes)
|
||||
* Version major/minor (2 bytes)
|
||||
* file length (long)
|
||||
* free list start (unsigned int)
|
||||
* is mounted (unsigned short) 0 = no, 1 = yes
|
||||
* span size (unsigned short)
|
||||
* block size (unsigned int)
|
||||
*
|
||||
* Metaindex skiplist is on page 2
|
||||
*
|
||||
* Pages are 1 KB and are numbered starting from 1.
|
||||
* e.g. the Metaindex skiplist is at offset 1024 bytes
|
||||
*/
|
||||
public class BlockFile implements Closeable {
|
||||
public static final int PAGESIZE = 1024;
|
||||
public static final long OFFSET_MOUNTED = 20;
|
||||
public final Log log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
|
||||
|
||||
public final RandomAccessInterface file;
|
||||
|
||||
private static final int MAJOR = 0x01;
|
||||
private static final int MINOR = 0x02;
|
||||
private static final int MIN_MAJOR = 0x01;
|
||||
private static final int MIN_MINOR = 0x01;
|
||||
// I2P changed magic number, format changed, magic numbers now on all pages
|
||||
private static final long MAGIC_BASE = 0x3141de4932500000L; // 0x3141de I 2 P 00 00
|
||||
private static final long MAGIC = MAGIC_BASE | (MAJOR << 8) | MINOR;
|
||||
private long magicBytes = MAGIC;
|
||||
public static final int MAGIC_CONT = 0x434f4e54; // "CONT"
|
||||
public static final int METAINDEX_PAGE = 2;
|
||||
/** 2**32 pages of 1024 bytes each, more or less */
|
||||
private static final long MAX_LEN = (2l << (32 + 10)) - 1;
|
||||
|
||||
/** new BlockFile length, containing a superblock page and a metaindex page. */
|
||||
private long fileLen = PAGESIZE * 2;
|
||||
private int freeListStart = 0;
|
||||
private int mounted = 0;
|
||||
public int spanSize = 16;
|
||||
|
||||
/** I2P was the file locked when we opened it? */
|
||||
private final boolean _wasMounted;
|
||||
|
||||
private final BSkipList<String, Integer> metaIndex;
|
||||
private boolean _isClosed;
|
||||
/** cached list of free pages, only valid if freListStart > 0 */
|
||||
private FreeListBlock flb;
|
||||
private final HashMap<String, BSkipList> openIndices = new HashMap<String, BSkipList>();
|
||||
|
||||
private void mount() throws IOException {
|
||||
file.seek(BlockFile.OFFSET_MOUNTED);
|
||||
mounted = 1;
|
||||
file.writeShort(mounted);
|
||||
}
|
||||
|
||||
private void writeSuperBlock() throws IOException {
|
||||
file.seek(0);
|
||||
file.writeLong( magicBytes);
|
||||
file.writeLong( fileLen);
|
||||
file.writeInt( freeListStart);
|
||||
file.writeShort(mounted);
|
||||
file.writeShort(spanSize);
|
||||
// added in version 1.2
|
||||
file.writeInt(PAGESIZE);
|
||||
}
|
||||
|
||||
private void readSuperBlock() throws IOException {
|
||||
file.seek(0);
|
||||
magicBytes = file.readLong();
|
||||
fileLen = file.readLong();
|
||||
freeListStart = file.readUnsignedInt();
|
||||
mounted = file.readUnsignedShort();
|
||||
spanSize = file.readUnsignedShort();
|
||||
// assume 1024 page size
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an integrity check on the blockfile and all the skiplists in it.
|
||||
*
|
||||
* WARNING:
|
||||
* This only works on skiplists using UTF8StringBytes as a key
|
||||
* serializer, unless the exception has been coded in bfck below.
|
||||
* Will CORRUPT other skiplists.
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 1) {
|
||||
System.err.println("Usage: BlockFile file");
|
||||
return;
|
||||
}
|
||||
boolean init = !(new File(args[0])).exists();
|
||||
try {
|
||||
RAIFile raif = new RAIFile(new File(args[0]), true, true);
|
||||
BlockFile bf = new BlockFile(raif, init);
|
||||
bf.bfck(true);
|
||||
bf.close();
|
||||
raif.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write bytes
|
||||
* This will allocate additional continuation pages as necessary.
|
||||
*
|
||||
* @param data data to write
|
||||
* @param page current page
|
||||
* @param curPageOff in (current) and out (new) parameter at index 0
|
||||
* @param nextPage in (current) and out (new) parameter at index 0
|
||||
* @return current page
|
||||
*/
|
||||
public int writeMultiPageData(byte[] data, int page, int[] curPageOff, int[] nextPage) throws IOException {
|
||||
int pageCounter = curPageOff[0];
|
||||
int curNextPage = nextPage[0];
|
||||
int curPage = page;
|
||||
int dct = 0;
|
||||
while(dct < data.length) {
|
||||
int len = PAGESIZE - pageCounter;
|
||||
if(len <= 0) {
|
||||
if(curNextPage==0) {
|
||||
curNextPage = this.allocPage();
|
||||
BlockFile.pageSeek(this.file, curNextPage);
|
||||
this.file.writeInt(MAGIC_CONT);
|
||||
this.file.writeInt(0);
|
||||
BlockFile.pageSeek(this.file, curPage);
|
||||
this.file.skipBytes(4); // skip magic
|
||||
this.file.writeInt(curNextPage);
|
||||
}
|
||||
BlockFile.pageSeek(this.file, curNextPage);
|
||||
curPage = curNextPage;
|
||||
int magic = this.file.readInt();
|
||||
if (magic != MAGIC_CONT)
|
||||
throw new IOException("Bad SkipSpan continuation magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage);
|
||||
curNextPage = this.file.readUnsignedInt();
|
||||
pageCounter = BSkipSpan.CONT_HEADER_LEN;
|
||||
len = PAGESIZE - pageCounter;
|
||||
}
|
||||
this.file.write(data, dct, Math.min(len, data.length - dct));
|
||||
pageCounter += Math.min(len, data.length - dct);
|
||||
dct += Math.min(len, data.length - dct);
|
||||
}
|
||||
nextPage[0] = curNextPage;
|
||||
curPageOff[0] = pageCounter;
|
||||
return curPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes
|
||||
*
|
||||
* @param arr fill this array fully with data
|
||||
* @param page current page
|
||||
* @param curPageOff in (current) and out (new) parameter at index 0
|
||||
* @param nextPage in (current) and out (new) parameter at index 0
|
||||
* @return current page
|
||||
*/
|
||||
public int readMultiPageData(byte[] arr, int page, int[] curPageOff, int[] nextPage) throws IOException {
|
||||
int pageCounter = curPageOff[0];
|
||||
int curNextPage = nextPage[0];
|
||||
int curPage = page;
|
||||
int dct = 0;
|
||||
while(dct < arr.length) {
|
||||
int len = PAGESIZE - pageCounter;
|
||||
if(len <= 0) {
|
||||
if (curNextPage <= 0)
|
||||
throw new IOException("not enough pages to read data still need " + (arr.length - dct));
|
||||
BlockFile.pageSeek(this.file, curNextPage);
|
||||
int magic = this.file.readInt();
|
||||
if (magic != MAGIC_CONT)
|
||||
throw new IOException("Bad SkipSpan continuation magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage);
|
||||
curPage = curNextPage;
|
||||
curNextPage = this.file.readUnsignedInt();
|
||||
pageCounter = BSkipSpan.CONT_HEADER_LEN;
|
||||
len = PAGESIZE - pageCounter;
|
||||
}
|
||||
int res = this.file.read(arr, dct, Math.min(len, arr.length - dct));
|
||||
if(res == -1) { throw new IOException(); }
|
||||
pageCounter += Math.min(len, arr.length - dct);
|
||||
dct += res;
|
||||
}
|
||||
nextPage[0] = curNextPage;
|
||||
curPageOff[0] = pageCounter;
|
||||
return curPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip length bytes
|
||||
* The same as readMultiPageData() without returning a result
|
||||
*
|
||||
* @param length number of bytes to skip
|
||||
* @param page current page
|
||||
* @param curPageOff in (current) and out (new) parameter at index 0
|
||||
* @param nextPage in (current) and out (new) parameter at index 0
|
||||
* @return current page
|
||||
*/
|
||||
public int skipMultiPageBytes(int length, int page, int[] curPageOff, int[] nextPage) throws IOException {
|
||||
int pageCounter = curPageOff[0];
|
||||
int curNextPage = nextPage[0];
|
||||
int curPage = page;
|
||||
int dct = 0;
|
||||
while(dct < length) {
|
||||
int len = PAGESIZE - pageCounter;
|
||||
if(len <= 0) {
|
||||
if (curNextPage <= 0)
|
||||
throw new IOException("not enough pages to skip");
|
||||
BlockFile.pageSeek(this.file, curNextPage);
|
||||
int magic = this.file.readInt();
|
||||
if (magic != MAGIC_CONT)
|
||||
throw new IOException("Bad SkipSpan continuation magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage);
|
||||
curPage = curNextPage;
|
||||
curNextPage = this.file.readUnsignedInt();
|
||||
pageCounter = BSkipSpan.CONT_HEADER_LEN;
|
||||
len = PAGESIZE - pageCounter;
|
||||
}
|
||||
int res = Math.min(len, length - dct);
|
||||
this.file.skipBytes(res);
|
||||
pageCounter += res;
|
||||
dct += res;
|
||||
}
|
||||
nextPage[0] = curNextPage;
|
||||
curPageOff[0] = pageCounter;
|
||||
return curPage;
|
||||
}
|
||||
|
||||
/** Use this constructor with a readonly RAI for a readonly blockfile */
|
||||
public BlockFile(RandomAccessInterface rai) throws IOException { this(rai, false); }
|
||||
|
||||
/** RAF must be writable */
|
||||
public BlockFile(RandomAccessFile raf) throws IOException { this(new RAIFile(raf), false); }
|
||||
|
||||
/** RAF must be writable */
|
||||
public BlockFile(RandomAccessFile raf, boolean init) throws IOException { this(new RAIFile(raf), init); }
|
||||
|
||||
/** File must be writable */
|
||||
public BlockFile(File f, boolean init) throws IOException { this(new RAIFile(f, true, true), init); }
|
||||
|
||||
/** Use this constructor with a readonly RAI and init = false for a readonly blockfile */
|
||||
public BlockFile(RandomAccessInterface rai, boolean init) throws IOException {
|
||||
if(rai==null) { throw new NullPointerException(); }
|
||||
|
||||
file = rai;
|
||||
|
||||
if(init) {
|
||||
file.setLength(fileLen);
|
||||
writeSuperBlock();
|
||||
BSkipList.init(this, METAINDEX_PAGE, spanSize);
|
||||
}
|
||||
|
||||
readSuperBlock();
|
||||
if(magicBytes != MAGIC) {
|
||||
if ((magicBytes & MAGIC_BASE) == MAGIC_BASE) {
|
||||
long major = (magicBytes >> 8) & 0xff;
|
||||
long minor = magicBytes & 0xff;
|
||||
if (major < MIN_MAJOR ||
|
||||
(major == MIN_MAJOR && minor < MIN_MINOR))
|
||||
throw new IOException("Expected " + MAJOR + '.' + MINOR +
|
||||
" but got " + major + '.' + minor);
|
||||
} else {
|
||||
throw new IOException("Bad magic number");
|
||||
}
|
||||
}
|
||||
_wasMounted = mounted != 0;
|
||||
if (_wasMounted)
|
||||
log.warn("Warning - file was not previously closed");
|
||||
if(fileLen != file.length())
|
||||
throw new IOException("Expected file length " + fileLen +
|
||||
" but actually " + file.length());
|
||||
if (rai.canWrite())
|
||||
mount();
|
||||
|
||||
metaIndex = new BSkipList<String, Integer>(spanSize, this, METAINDEX_PAGE, new StringBytes(), new IntBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P was the file locked when we opened it?
|
||||
* @since 0.8.8
|
||||
*/
|
||||
public boolean wasMounted() {
|
||||
return _wasMounted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to any page but the superblock.
|
||||
* Page 1 is the superblock, must use file.seek(0) to get there.
|
||||
* @param page >= 2
|
||||
*/
|
||||
public static void pageSeek(RandomAccessInterface file, int page) throws IOException {
|
||||
if (page < METAINDEX_PAGE)
|
||||
throw new IOException("Negative page or superblock access attempt: " + page);
|
||||
file.seek((page - 1L) * PAGESIZE );
|
||||
}
|
||||
|
||||
public int allocPage() throws IOException {
|
||||
if(freeListStart != 0) {
|
||||
try {
|
||||
if (flb == null)
|
||||
flb = new FreeListBlock(file, freeListStart);
|
||||
if(!flb.isEmpty()) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Alloc from " + flb);
|
||||
return flb.takePage();
|
||||
} else {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Alloc returning empty " + flb);
|
||||
freeListStart = flb.getNextPage();
|
||||
writeSuperBlock();
|
||||
int rv = flb.page;
|
||||
flb = null;
|
||||
return rv;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.error("Discarding corrupt free list block page " + freeListStart, ioe);
|
||||
freeListStart = 0;
|
||||
}
|
||||
}
|
||||
long offset = file.length();
|
||||
fileLen = offset + PAGESIZE;
|
||||
file.setLength(fileLen);
|
||||
writeSuperBlock();
|
||||
return (int) ((offset / PAGESIZE) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page to the free list. The file is never shrunk.
|
||||
* TODO: Reclaim free pages at end of file, or even do a full compaction.
|
||||
* Does not throw exceptions; logs on failure.
|
||||
*/
|
||||
public void freePage(int page) {
|
||||
if (page <= METAINDEX_PAGE) {
|
||||
log.error("Bad page free attempt: " + page);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if(freeListStart == 0) {
|
||||
freeListStart = page;
|
||||
FreeListBlock.initPage(file, page);
|
||||
writeSuperBlock();
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Freed page " + page + " as new FLB");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (flb == null)
|
||||
flb = new FreeListBlock(file, freeListStart);
|
||||
if(flb.isFull()) {
|
||||
// Make the free page a new FLB
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Full: " + flb);
|
||||
FreeListBlock.initPage(file, page);
|
||||
if(flb.getNextPage() == 0) {
|
||||
// Put it at the tail.
|
||||
// Next free will make a new FLB at the head,
|
||||
// so we have one more FLB than we need.
|
||||
flb.setNextPage(page);
|
||||
} else {
|
||||
// Put it at the head
|
||||
flb = new FreeListBlock(file, page);
|
||||
flb.setNextPage(freeListStart);
|
||||
freeListStart = page;
|
||||
writeSuperBlock();
|
||||
}
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Freed page " + page + " to full " + flb);
|
||||
return;
|
||||
}
|
||||
flb.addPage(page);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Freed page " + page + " to " + flb);
|
||||
} catch (IOException ioe) {
|
||||
log.error("Discarding corrupt free list block page " + freeListStart, ioe);
|
||||
freeListStart = page;
|
||||
FreeListBlock.initPage(file, page);
|
||||
writeSuperBlock();
|
||||
flb = null;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.error("Error freeing page: " + page, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a skiplist if it exists.
|
||||
* Returns null if the skiplist does not exist.
|
||||
* Empty skiplists are not preserved after close.
|
||||
*
|
||||
* If the file is writable, this runs an integrity check and repair
|
||||
* on first open.
|
||||
*
|
||||
* @return null if not found
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K extends Comparable<? super K>, V> BSkipList<K, V> getIndex(String name, Serializer<K> key, Serializer<V> val) throws IOException {
|
||||
// added I2P
|
||||
BSkipList<K, V> bsl = (BSkipList<K, V>) openIndices.get(name);
|
||||
if (bsl != null)
|
||||
return bsl;
|
||||
|
||||
Integer page = metaIndex.get(name);
|
||||
if (page == null) { return null; }
|
||||
bsl = new BSkipList<K, V>(spanSize, this, page.intValue(), key, val, true);
|
||||
if (file.canWrite()) {
|
||||
log.info("Checking skiplist " + name + " in blockfile " + file);
|
||||
if (bsl.bslck(true, false))
|
||||
log.logAlways(Log.WARN, "Repaired skiplist " + name + " in blockfile " + file);
|
||||
else
|
||||
log.info("No errors in skiplist " + name + " in blockfile " + file);
|
||||
}
|
||||
openIndices.put(name, bsl);
|
||||
return bsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and open a new skiplist if it does not exist.
|
||||
* Throws IOException if it already exists.
|
||||
*
|
||||
* @throws IOException if already exists or other errors
|
||||
*/
|
||||
public <K extends Comparable<? super K>, V> BSkipList<K, V> makeIndex(String name, Serializer<K> key, Serializer<V> val) throws IOException {
|
||||
if(metaIndex.get(name) != null) { throw new IOException("Index already exists"); }
|
||||
int page = allocPage();
|
||||
metaIndex.put(name, Integer.valueOf(page));
|
||||
BSkipList.init(this, page, spanSize);
|
||||
BSkipList<K, V> bsl = new BSkipList<K, V>(spanSize, this, page, key, val, true);
|
||||
openIndices.put(name, bsl);
|
||||
return bsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a skiplist if it exists.
|
||||
* Must be open. Throws IOException if exists but is closed.
|
||||
* Broken before 0.9.26.
|
||||
*
|
||||
* @throws IOException if it is closed.
|
||||
*/
|
||||
public void delIndex(String name) throws IOException {
|
||||
if (metaIndex.get(name) == null)
|
||||
return;
|
||||
BSkipList bsl = openIndices.get(name);
|
||||
if (bsl == null)
|
||||
throw new IOException("Cannot delete closed skiplist, open it first: " + name);
|
||||
bsl.delete();
|
||||
openIndices.remove(name);
|
||||
metaIndex.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a skiplist if it is open.
|
||||
*
|
||||
* Added I2P
|
||||
*/
|
||||
public void closeIndex(String name) {
|
||||
BSkipList bsl = openIndices.remove(name);
|
||||
if (bsl != null)
|
||||
bsl.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reformat a skiplist with new Serializers if it exists.
|
||||
* The skiplist must be closed.
|
||||
* Throws IOException if the skiplist is open.
|
||||
* The skiplist will remain closed after completion.
|
||||
*
|
||||
* @throws IOException if it is open or on errors
|
||||
* @since 0.9.26
|
||||
*/
|
||||
public <K extends Comparable<? super K>, V> void reformatIndex(String name, Serializer<K> oldKey, Serializer<V> oldVal,
|
||||
Serializer<K> newKey, Serializer<V> newVal) throws IOException {
|
||||
if (openIndices.containsKey(name))
|
||||
throw new IOException("Cannot reformat open skiplist " + name);
|
||||
BSkipList<K, V> old = getIndex(name, oldKey, oldVal);
|
||||
if (old == null)
|
||||
return;
|
||||
long start = System.currentTimeMillis();
|
||||
String tmpName = "---tmp---" + name + "---tmp---";
|
||||
BSkipList<K, V> tmp = getIndex(tmpName, newKey, newVal);
|
||||
if (tmp != null) {
|
||||
log.logAlways(Log.WARN, "Continuing on aborted reformat of list " + name);
|
||||
} else {
|
||||
tmp = makeIndex(tmpName, newKey, newVal);
|
||||
}
|
||||
|
||||
// It could be much more efficient to do this at the
|
||||
// SkipSpan layer but that's way too hard.
|
||||
final int loop = 32;
|
||||
List<K> keys = new ArrayList<K>(loop);
|
||||
List<V> vals = new ArrayList<V>(loop);
|
||||
while (true) {
|
||||
SkipIterator<K, V> iter = old.iterator();
|
||||
for (int i = 0; iter.hasNext() && i < loop; i++) {
|
||||
try {
|
||||
keys.add(iter.nextKey());
|
||||
vals.add(iter.next());
|
||||
} catch (NoSuchElementException nsee) {
|
||||
throw new IOException("Unable to reformat corrupt list " + name, nsee);
|
||||
}
|
||||
}
|
||||
// save state, as deleting corrupts the iterator
|
||||
boolean done = !iter.hasNext();
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
tmp.put(keys.get(i), vals.get(i));
|
||||
}
|
||||
for (int i = keys.size() - 1; i >= 0; i--) {
|
||||
old.remove(keys.get(i));
|
||||
}
|
||||
if (done)
|
||||
break;
|
||||
keys.clear();
|
||||
vals.clear();
|
||||
}
|
||||
|
||||
delIndex(name);
|
||||
closeIndex(name);
|
||||
closeIndex(tmpName);
|
||||
Integer page = metaIndex.get(tmpName);
|
||||
metaIndex.put(name, page);
|
||||
metaIndex.remove(tmpName);
|
||||
if (log.shouldWarn())
|
||||
log.warn("reformatted list: " + name + " in " +
|
||||
(System.currentTimeMillis() - start) + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all open skiplists and then the blockfile itself.
|
||||
*
|
||||
* Note (I2P)
|
||||
* Does NOT close the RAF / RAI.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
// added I2P
|
||||
if (_isClosed)
|
||||
return;
|
||||
_isClosed = true;
|
||||
metaIndex.close();
|
||||
|
||||
Set<String> oi = openIndices.keySet();
|
||||
Iterator<String> i = oi.iterator();
|
||||
Object k;
|
||||
while(i.hasNext()) {
|
||||
k = i.next();
|
||||
BSkipList bsl = openIndices.get(k);
|
||||
bsl.close();
|
||||
}
|
||||
|
||||
// Unmount.
|
||||
if (file.canWrite()) {
|
||||
file.seek(BlockFile.OFFSET_MOUNTED);
|
||||
file.writeShort(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an integrity check on the blockfile and all the skiplists in it
|
||||
* @return true if the levels were modified.
|
||||
*/
|
||||
public boolean bfck(boolean fix) {
|
||||
if (log.shouldLog(Log.INFO)) {
|
||||
log.info("magic bytes " + magicBytes);
|
||||
log.info("fileLen " + fileLen);
|
||||
log.info("freeListStart " + freeListStart);
|
||||
log.info("mounted " + mounted);
|
||||
log.info("spanSize " + spanSize);
|
||||
log.info("Metaindex");
|
||||
log.info("Checking meta index in blockfile " + file);
|
||||
}
|
||||
boolean rv = metaIndex.bslck(fix, true);
|
||||
if (rv) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Repaired meta index in blockfile " + file);
|
||||
} else {
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("No errors in meta index in blockfile " + file);
|
||||
}
|
||||
int items = 0;
|
||||
for (SkipIterator iter = metaIndex.iterator(); iter.hasNext(); ) {
|
||||
String slname = (String) iter.nextKey();
|
||||
Integer page = (Integer) iter.next();
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("List " + slname + " page " + page);
|
||||
try {
|
||||
// This uses IdentityBytes, so the value class won't be right, but at least
|
||||
// it won't fail the out-of-order check
|
||||
boolean fail;
|
||||
if (slname.equals("%%__REVERSE__%%")) {
|
||||
Serializer<Integer> keyser = new IntBytes();
|
||||
fail = getIndex(slname, keyser, new IdentityBytes()) == null;
|
||||
} else {
|
||||
Serializer<String> keyser = new UTF8StringBytes();
|
||||
fail = getIndex(slname, keyser, new IdentityBytes()) == null;
|
||||
}
|
||||
if (fail) {
|
||||
log.error("Can't find list? " + slname);
|
||||
continue;
|
||||
}
|
||||
// The check is now done in getIndex(), no need to do here...
|
||||
// but we can't get the return value of the check here.
|
||||
items++;
|
||||
} catch (IOException ioe) {
|
||||
log.error("Error with list " + slname, ioe);
|
||||
}
|
||||
}
|
||||
log.info("Checked meta index and " + items + " skiplists");
|
||||
if(freeListStart != 0) {
|
||||
try {
|
||||
if (flb == null)
|
||||
flb = new FreeListBlock(file, freeListStart);
|
||||
flb.flbck(true);
|
||||
} catch (IOException ioe) {
|
||||
log.error("Free list error", ioe);
|
||||
}
|
||||
} else {
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("No freelist");
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.metanotion.io.RandomAccessInterface;
|
||||
|
||||
/**
|
||||
* On-disk format:
|
||||
*<pre>
|
||||
* Magic number (long)
|
||||
* next freelist block page (unsigned int)
|
||||
* size (unsigned int)
|
||||
* that many free pages (unsigned ints)
|
||||
*</pre>
|
||||
*
|
||||
* Always fits on one page.
|
||||
*
|
||||
* Free page format:
|
||||
*<pre>
|
||||
* Magic number (long)
|
||||
*</pre>
|
||||
*/
|
||||
class FreeListBlock {
|
||||
private static final long MAGIC = 0x2366724c69737423l; // "#frList#"
|
||||
private static final long MAGIC_FREE = 0x7e2146524545217el; // "~!FREE!~"
|
||||
private static final int HEADER_LEN = 16;
|
||||
private static final int MAX_SIZE = (BlockFile.PAGESIZE - HEADER_LEN) / 4;
|
||||
|
||||
public final int page;
|
||||
private int nextPage;
|
||||
private int len;
|
||||
private final int[] branches;
|
||||
private final RandomAccessInterface file;
|
||||
|
||||
public FreeListBlock(RandomAccessInterface file, int startPage) throws IOException {
|
||||
this.file = file;
|
||||
this.page = startPage;
|
||||
BlockFile.pageSeek(file, startPage);
|
||||
long magic = file.readLong();
|
||||
if (magic != MAGIC)
|
||||
throw new IOException("Bad freelist magic number 0x" + Long.toHexString(magic) + " on page " + startPage);
|
||||
nextPage = file.readUnsignedInt();
|
||||
len = file.readUnsignedInt();
|
||||
if (len > MAX_SIZE)
|
||||
throw new IOException("Bad freelist size " + len);
|
||||
branches = new int[MAX_SIZE];
|
||||
if(len > 0) {
|
||||
int good = 0;
|
||||
for(int i=0;i<len;i++) {
|
||||
int fpg = file.readInt();
|
||||
if (fpg > BlockFile.METAINDEX_PAGE)
|
||||
branches[good++] = fpg;
|
||||
}
|
||||
if (good != len) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
|
||||
log.error((len - good) + " bad pages in " + this);
|
||||
len = good;
|
||||
writeBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBlock() throws IOException {
|
||||
BlockFile.pageSeek(file, page);
|
||||
file.writeLong(MAGIC);
|
||||
file.writeInt(nextPage);
|
||||
file.writeInt(len);
|
||||
for(int i=0;i<len;i++) { file.writeInt(branches[i]); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the length only
|
||||
*/
|
||||
private void writeLen() throws IOException {
|
||||
BlockFile.pageSeek(file, page);
|
||||
file.skipBytes(12);
|
||||
file.writeInt(len);
|
||||
}
|
||||
|
||||
public int getNextPage() {
|
||||
return nextPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and write the next page only
|
||||
*/
|
||||
public void setNextPage(int nxt) throws IOException {
|
||||
nextPage = nxt;
|
||||
BlockFile.pageSeek(file, page);
|
||||
file.skipBytes(8);
|
||||
file.writeInt(nxt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the length and new page only
|
||||
*/
|
||||
private void writeFreePage() throws IOException {
|
||||
BlockFile.pageSeek(file, page);
|
||||
file.skipBytes(12);
|
||||
file.writeInt(len);
|
||||
if (len > 1)
|
||||
file.skipBytes((len - 1) * 4);
|
||||
file.writeInt(branches[len - 1]);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return len <= 0;
|
||||
}
|
||||
|
||||
public boolean isFull() {
|
||||
return len >= MAX_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds free page and writes new len to disk
|
||||
* @throws IllegalStateException if full
|
||||
*/
|
||||
public void addPage(int freePage) throws IOException {
|
||||
if (len >= MAX_SIZE)
|
||||
throw new IllegalStateException("full");
|
||||
if (getMagic(freePage) == MAGIC_FREE) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
|
||||
log.error("Double free page " + freePage, new Exception());
|
||||
return;
|
||||
}
|
||||
branches[len++] = freePage;
|
||||
markFree(freePage);
|
||||
writeFreePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes next page and writes new len to disk
|
||||
* @throws IllegalStateException if empty
|
||||
*/
|
||||
public int takePage() throws IOException {
|
||||
if (len <= 0)
|
||||
throw new IllegalStateException("empty");
|
||||
len--;
|
||||
writeLen();
|
||||
int rv = branches[len];
|
||||
if (rv <= BlockFile.METAINDEX_PAGE)
|
||||
// shouldn't happen
|
||||
throw new IOException("Bad free page " + rv);
|
||||
long magic = getMagic(rv);
|
||||
if (magic != MAGIC_FREE)
|
||||
// TODO keep trying until empty
|
||||
throw new IOException("Bad free page magic number 0x" + Long.toHexString(magic) + " on page " + rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void markFree(int freePage) throws IOException {
|
||||
BlockFile.pageSeek(file, freePage);
|
||||
file.writeLong(MAGIC_FREE);
|
||||
}
|
||||
|
||||
private long getMagic(int freePage) throws IOException {
|
||||
BlockFile.pageSeek(file, freePage);
|
||||
long magic = file.readLong();
|
||||
return magic;
|
||||
}
|
||||
|
||||
public static void initPage(RandomAccessInterface file, int page) throws IOException {
|
||||
BlockFile.pageSeek(file, page);
|
||||
file.writeLong(MAGIC);
|
||||
file.writeInt(0);
|
||||
file.writeInt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public boolean flbck(boolean fix) throws IOException {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
|
||||
log.info(toString());
|
||||
if (nextPage > 0)
|
||||
(new FreeListBlock(file, nextPage)).flbck(fix);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FLB with " + len + " / " + MAX_SIZE + " page " + page + " next page " + nextPage;
|
||||
}
|
||||
}
|
@ -1,439 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.metanotion.io.block.BlockFile;
|
||||
import net.metanotion.util.skiplist.SkipList;
|
||||
import net.metanotion.util.skiplist.SkipLevels;
|
||||
import net.metanotion.util.skiplist.SkipSpan;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* On-disk format:
|
||||
*<pre>
|
||||
* Magic number (long)
|
||||
* max height (unsigned short)
|
||||
* non-null height (unsigned short)
|
||||
* span page (unsigned int)
|
||||
* height number of level pages (unsigned ints)
|
||||
*</pre>
|
||||
*
|
||||
* Always fits on one page.
|
||||
*/
|
||||
public class BSkipLevels<K extends Comparable<? super K>, V> extends SkipLevels<K, V> {
|
||||
private static final long MAGIC = 0x42534c6576656c73l; // "BSLevels"
|
||||
static final int HEADER_LEN = 16;
|
||||
public final int levelPage;
|
||||
public final int spanPage;
|
||||
public final BlockFile bf;
|
||||
private final BSkipList<K, V> bsl;
|
||||
private boolean isKilled;
|
||||
// the level pages, passed from the constructor to initializeLevels(),
|
||||
// NOT kept up to date
|
||||
private final int[] lps;
|
||||
|
||||
/**
|
||||
* Non-recursive initializer initializeLevels()
|
||||
* MUST be called on the first BSkipLevel in the skiplist
|
||||
* after the constructor, unless it's a new empty
|
||||
* level and init() was previously called.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public BSkipLevels(BlockFile bf, int levelPage, BSkipList<K, V> bsl) throws IOException {
|
||||
this.levelPage = levelPage;
|
||||
this.bf = bf;
|
||||
this.bsl = bsl;
|
||||
|
||||
BlockFile.pageSeek(bf.file, levelPage);
|
||||
long magic = bf.file.readLong();
|
||||
if (magic != MAGIC)
|
||||
throw new IOException("Bad SkipLevels magic number 0x" + Long.toHexString(magic) + " on page " + levelPage);
|
||||
|
||||
bsl.levelHash.put(Integer.valueOf(this.levelPage), this);
|
||||
|
||||
int maxLen = bf.file.readUnsignedShort();
|
||||
int nonNull = bf.file.readUnsignedShort();
|
||||
if(maxLen < 1 || maxLen > MAX_SIZE || nonNull > maxLen)
|
||||
throw new IOException("Invalid Level Skip size " + nonNull + " / " + maxLen);
|
||||
spanPage = bf.file.readUnsignedInt();
|
||||
bottom = bsl.spanHash.get(Integer.valueOf(spanPage));
|
||||
if (bottom == null) {
|
||||
// FIXME recover better?
|
||||
bf.log.error("No span found in cache???");
|
||||
throw new IOException("No span found in cache???");
|
||||
}
|
||||
|
||||
this.levels = (BSkipLevels<K, V>[]) new BSkipLevels[maxLen];
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Reading New BSkipLevels with " + nonNull + " / " + maxLen + " valid levels page " + levelPage +
|
||||
" in skiplist " + bsl);
|
||||
// We have to read now because new BSkipLevels() will move the file pointer
|
||||
lps = new int[nonNull];
|
||||
for(int i = 0; i < nonNull; i++) {
|
||||
lps[i] = bf.file.readUnsignedInt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-recursive initializer.
|
||||
* MUST be called on the first BSkipLevel in the skiplist
|
||||
* after the constructor, unless it's a new empty
|
||||
* level and init() was previously called.
|
||||
* Only call on the first skiplevel in the list!
|
||||
*
|
||||
* @since 0.9.20
|
||||
*/
|
||||
public void initializeLevels() {
|
||||
List<BSkipLevels<K, V>> toInit = new ArrayList<BSkipLevels<K, V>>(32);
|
||||
List<BSkipLevels<K, V>> nextInit = new ArrayList<BSkipLevels<K, V>>(32);
|
||||
initializeLevels(toInit);
|
||||
while (!toInit.isEmpty()) {
|
||||
for (BSkipLevels<K, V> bsl : toInit) {
|
||||
bsl.initializeLevels(nextInit);
|
||||
}
|
||||
List<BSkipLevels<K, V>> tmp = toInit;
|
||||
toInit = nextInit;
|
||||
nextInit = tmp;
|
||||
nextInit.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-recursive initializer.
|
||||
* MUST be called after constructor.
|
||||
*
|
||||
* @param nextInit out parameter, next levels to initialize
|
||||
* @since 0.9.20
|
||||
*/
|
||||
private void initializeLevels(List<BSkipLevels<K, V>> nextInit) {
|
||||
boolean fail = false;
|
||||
for(int i = 0; i < lps.length; i++) {
|
||||
int lp = lps[i];
|
||||
if(lp != 0) {
|
||||
levels[i] = bsl.levelHash.get(Integer.valueOf(lp));
|
||||
if(levels[i] == null) {
|
||||
try {
|
||||
BSkipLevels<K, V> lev = new BSkipLevels<K, V>(bf, lp, bsl);
|
||||
levels[i] = lev;
|
||||
nextInit.add(lev);
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Corrupt database, bad level " + i +
|
||||
" at page " + lp, ioe);
|
||||
levels[i] = null;
|
||||
fail = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
K ourKey = key();
|
||||
K nextKey = levels[i].key();
|
||||
if (ourKey != null && nextKey != null &&
|
||||
ourKey.compareTo(nextKey) >= 0) {
|
||||
bf.log.warn("Corrupt database, level out of order " + this +
|
||||
' ' + print() +
|
||||
" i = " + i + ' ' + levels[i]);
|
||||
// This will be fixed in blvlfix() via BlockFile.getIndex()
|
||||
//levels[i] = null;
|
||||
//fail = true;
|
||||
}
|
||||
// TODO also check that the level[] array is not out-of-order
|
||||
} else {
|
||||
if (bf.log.shouldLog(Log.WARN))
|
||||
bf.log.warn(this + " i = " + i + " of " +
|
||||
lps.length + " / " + levels.length +
|
||||
" valid levels but page is zero");
|
||||
levels[i] = null;
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
if (fail && bf.file.canWrite()) {
|
||||
// corruption is actually fixed in blvlfix() via BlockFile.getIndex()
|
||||
// after instantiation is complete
|
||||
bf.log.error("Repairing corruption of " + this +
|
||||
' ' + print());
|
||||
flush();
|
||||
// if the removed levels have no other links to them, they and their data
|
||||
// are lost forever, but the alternative is infinite loops / stack overflows
|
||||
// in SkipSpan.
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(BlockFile bf, int page, int spanPage, int maxHeight) throws IOException {
|
||||
BlockFile.pageSeek(bf.file, page);
|
||||
bf.file.writeLong(MAGIC);
|
||||
bf.file.writeShort((short) maxHeight);
|
||||
bf.file.writeShort(0);
|
||||
bf.file.writeInt(spanPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
if (isKilled) {
|
||||
bf.log.error("Already killed!! " + this, new Exception());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
BlockFile.pageSeek(bf.file, levelPage);
|
||||
bf.file.writeLong(MAGIC);
|
||||
bf.file.writeShort((short) levels.length);
|
||||
int i = 0;
|
||||
for( ; i < levels.length; i++) {
|
||||
if(levels[i] == null)
|
||||
break;
|
||||
}
|
||||
bf.file.writeShort(i);
|
||||
bf.file.writeInt(((BSkipSpan<K, V>) bottom).page);
|
||||
for(int j = 0; j < i; j++) {
|
||||
bf.file.writeInt(((BSkipLevels<K, V>) levels[j]).levelPage);
|
||||
}
|
||||
} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void killInstance() {
|
||||
if (isKilled) {
|
||||
bf.log.error("Already killed!! " + this, new Exception());
|
||||
return;
|
||||
}
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Killing " + this + ' ' + print() /* , new Exception() */ );
|
||||
isKilled = true;
|
||||
bsl.levelHash.remove(Integer.valueOf(levelPage));
|
||||
bf.freePage(levelPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkipLevels<K, V> newInstance(int levels, SkipSpan<K, V> ss, SkipList<K, V> sl) {
|
||||
try {
|
||||
BSkipSpan<K, V> bss = (BSkipSpan<K, V>) ss;
|
||||
BSkipList<K, V> bsl = (BSkipList<K, V>) sl;
|
||||
int page = bf.allocPage();
|
||||
BSkipLevels.init(bf, page, bss.page, levels);
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("New BSkipLevels height " + levels + " page " + page);
|
||||
return new BSkipLevels<K, V>(bf, page, bsl);
|
||||
// do not need to call initLevels() here
|
||||
} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an integrity check on the skiplevels from the first,
|
||||
* or just fix it if fix == true.
|
||||
* Only call from the first level.
|
||||
* @return true if the levels were modified.
|
||||
*/
|
||||
@Override
|
||||
public boolean blvlck(boolean fix) {
|
||||
if (fix)
|
||||
return blvlfix();
|
||||
return blvlck(fix, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the levels.
|
||||
* Only call from the first level.
|
||||
* Primarily to fix nulls in levels caused by previous SkipLevels bug.
|
||||
* This should handle dups and loops and out-of-order levels too,
|
||||
* but those may cause problems before this in the constructor.
|
||||
* This is fast enough to call every time the skiplist is opened.
|
||||
* @return true if the levels were modified.
|
||||
* @since 0.8.8
|
||||
*/
|
||||
private boolean blvlfix() {
|
||||
TreeSet<SkipLevels<K, V>> lvls = new TreeSet<SkipLevels<K, V>>(new LevelComparator<K, V>());
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Starting level search");
|
||||
getAllLevels(this, lvls);
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Finished level search, found " + lvls.size() + " levels");
|
||||
if (!this.equals(lvls.last())) {
|
||||
bf.log.error("First level is out of order! " + print());
|
||||
// TODO switch stack and other fields for the skiplist - hard to test
|
||||
}
|
||||
// traverse the levels, back-to-front
|
||||
boolean rv = false;
|
||||
SkipLevels<K, V> after = null;
|
||||
for (SkipLevels<K, V> lv : lvls) {
|
||||
boolean modified = false;
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Checking " + lv.print());
|
||||
if (after != null) {
|
||||
int min = Math.min(after.levels.length, lv.levels.length);
|
||||
for (int i = 0; i < min; i++) {
|
||||
SkipLevels<K, V> cur = lv.levels[i];
|
||||
if (cur != after) {
|
||||
if (cur != null)
|
||||
bf.log.warn("Level " + i + " was wrong, fixing for " + lv.print());
|
||||
else
|
||||
bf.log.warn("Level " + i + " was null, fixing for " + lv.print());
|
||||
lv.levels[i] = after;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// last one
|
||||
for (int i = 0; i < lv.levels.length; i++) {
|
||||
if (lv.levels[i] != null) {
|
||||
lv.levels[i] = null;
|
||||
bf.log.warn("Last level " + i + " was non-null, fixing for " + lv.print());
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
lv.flush();
|
||||
rv = true;
|
||||
}
|
||||
after = lv;
|
||||
}
|
||||
if (bf.log.shouldLog(Log.INFO))
|
||||
bf.log.info("Checked " + lvls.size() + " levels");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Breadth-first, sortof
|
||||
* We assume everything is findable from the root level
|
||||
* @param l non-null
|
||||
* @param lvlSet out parameter, the result
|
||||
* @since 0.8.8
|
||||
*/
|
||||
private void getAllLevels(SkipLevels<K, V> l, Set<SkipLevels<K, V>> lvlSet) {
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("GAL " + l.print());
|
||||
// Do level 0 without recursion, on the assumption everything is findable
|
||||
// from the root
|
||||
SkipLevels<K, V> cur = l;
|
||||
while (cur != null && lvlSet.add(cur)) {
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Adding " + cur.print());
|
||||
if (!cur.equals(this) && cur.key() == null && bf.log.shouldLog(Log.WARN))
|
||||
bf.log.debug("Null KEY!!! " + cur.print());
|
||||
cur = cur.levels[0];
|
||||
}
|
||||
// If there were no nulls at level 0 in the middle,
|
||||
// i.e. there are no problems, this won't find anything
|
||||
for (int i = 1; i < l.levels.length; i++) {
|
||||
SkipLevels<K, V> lv = l.levels[i];
|
||||
if (lv != null && !lvlSet.contains(lv))
|
||||
getAllLevels(lv, lvlSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For sorting levels in blvlfix()
|
||||
* Sorts in REVERSE order.
|
||||
* @since 0.8.8
|
||||
*/
|
||||
private static class LevelComparator<K extends Comparable<? super K>, V> implements Comparator<SkipLevels<K, V>>, Serializable {
|
||||
public int compare(SkipLevels<K, V> l, SkipLevels<K, V> r) {
|
||||
K lk = l.key();
|
||||
K rk = r.key();
|
||||
if (lk == null && rk == null)
|
||||
return 0;
|
||||
if (lk == null)
|
||||
return 1;
|
||||
if (rk == null)
|
||||
return -1;
|
||||
// reverse!
|
||||
return rk.compareTo(lk);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursively walk through the levels at level 0
|
||||
* This needs work.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean blvlck(boolean fix, int width, SkipLevels<K, V>[] prevLevels) {
|
||||
bf.log.warn(" Skip level at width " + width);
|
||||
bf.log.warn(" levels " + this.levels.length);
|
||||
bf.log.warn(" first key " + this.key());
|
||||
bf.log.warn(" spanPage " + this.spanPage);
|
||||
bf.log.warn(" levelPage " + this.levelPage);
|
||||
SkipLevels<K, V> higher = null;
|
||||
for (int i = levels.length - 1; i >= 0; i--) {
|
||||
if (levels[i] != null) {
|
||||
bf.log.info(" level " + i + " -> " + levels[i].key() + " ");
|
||||
if (higher != null) {
|
||||
if (higher.key().compareTo(key()) < 0)
|
||||
bf.log.warn(" Higher level has lower key " + higher.key());
|
||||
}
|
||||
} else {
|
||||
bf.log.info(" level " + i + " empty");
|
||||
if (higher != null)
|
||||
bf.log.warn(" Higher level is not empty, key is " + higher.key());
|
||||
}
|
||||
}
|
||||
if (prevLevels != null) {
|
||||
int min = Math.min(prevLevels.length, levels.length);
|
||||
for (int i = 0; i < min; i++) {
|
||||
if (prevLevels[i] == this) {
|
||||
prevLevels[i] = levels[i];
|
||||
} else if (prevLevels[i] != null) {
|
||||
// skipping over us
|
||||
bf.log.warn(" Previous levels is non-null " + prevLevels[i].key() + " but not pointing to us at level " + i);
|
||||
// replace so we only get one error
|
||||
prevLevels[i] = levels[i];
|
||||
} else {
|
||||
// dead end in the middle
|
||||
if (levels[i] != null) {
|
||||
bf.log.warn(" Previous levels is null but we are non-null " + levels[i].key() + " at level " + i);
|
||||
// replace so we only get one error
|
||||
prevLevels[i] = levels[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prevLevels = (SkipLevels<K, V>[]) new SkipLevels[levels.length];
|
||||
System.arraycopy(levels, 0, prevLevels, 0, levels.length);
|
||||
}
|
||||
if (levels[0] != null)
|
||||
levels[0].blvlck(fix, width + 1, prevLevels);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String rv = "BSLevel height: " + levels.length + " page: " + levelPage + " span: " + bottom +
|
||||
" in skiplist " + bsl;
|
||||
if (isKilled)
|
||||
rv += " KILLED";
|
||||
return rv;
|
||||
}
|
||||
}
|
@ -1,278 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block.index;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
import net.metanotion.io.block.BlockFile;
|
||||
import net.metanotion.util.skiplist.*;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* On-disk format:
|
||||
*<pre>
|
||||
* Magic number (long)
|
||||
* first span page (unsigned int)
|
||||
* first level page (unsigned int)
|
||||
* size (unsigned int)
|
||||
* spans (unsigned int)
|
||||
* levels (unsigned int)
|
||||
*</pre>
|
||||
*
|
||||
* Always fits on one page.
|
||||
*/
|
||||
public class BSkipList<K extends Comparable<? super K>, V> extends SkipList<K, V> implements Closeable {
|
||||
private static final long MAGIC = 0x536b69704c697374l; // "SkipList"
|
||||
public int firstSpanPage = 0;
|
||||
public int firstLevelPage = 0;
|
||||
public int skipPage = 0;
|
||||
public final BlockFile bf;
|
||||
private boolean isClosed;
|
||||
|
||||
final HashMap<Integer, BSkipSpan<K, V>> spanHash = new HashMap<Integer, BSkipSpan<K, V>>();
|
||||
final HashMap<Integer, SkipLevels<K, V>> levelHash = new HashMap<Integer, SkipLevels<K, V>>();
|
||||
|
||||
private final boolean fileOnly;
|
||||
|
||||
public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer<K> key, Serializer<V> val) throws IOException {
|
||||
this(spanSize, bf, skipPage, key, val, false);
|
||||
}
|
||||
|
||||
public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer<K> key, Serializer<V> val, boolean fileOnly) throws IOException {
|
||||
if(spanSize < 1) { throw new RuntimeException("Span size too small"); }
|
||||
|
||||
this.skipPage = skipPage;
|
||||
this.bf = bf;
|
||||
|
||||
BlockFile.pageSeek(bf.file, skipPage);
|
||||
long magic = bf.file.readLong();
|
||||
if (magic != MAGIC)
|
||||
throw new IOException("Bad SkipList magic number 0x" + Long.toHexString(magic) + " on page " + skipPage);
|
||||
firstSpanPage = bf.file.readUnsignedInt();
|
||||
firstLevelPage = bf.file.readUnsignedInt();
|
||||
size = bf.file.readUnsignedInt();
|
||||
int spans = bf.file.readInt();
|
||||
int levelCount = bf.file.readInt();
|
||||
// two byte spansize as of version 1.2, ignore for now
|
||||
// int ss = bf.file.readUnsignedShort(); if (ss > 0) ...
|
||||
//System.out.println(size + " " + spans);
|
||||
|
||||
this.fileOnly = fileOnly;
|
||||
if (fileOnly)
|
||||
first = new IBSkipSpan<K, V>(bf, this, firstSpanPage, key, val);
|
||||
else
|
||||
first = new BSkipSpan<K, V>(bf, this, firstSpanPage, key, val);
|
||||
BSkipLevels<K, V> bstack = new BSkipLevels<K, V>(bf, firstLevelPage, this);
|
||||
bstack.initializeLevels();
|
||||
stack = bstack;
|
||||
int total = 0;
|
||||
for (BSkipSpan ss : spanHash.values()) {
|
||||
total += ss.nKeys;
|
||||
}
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Loaded " + this + " cached " + levelHash.size() + " levels and " + spanHash.size() + " spans with " + total + " entries");
|
||||
if (bf.file.canWrite() &&
|
||||
(levelCount != levelHash.size() || spans != spanHash.size() || size != total)) {
|
||||
if (bf.log.shouldLog(Log.WARN))
|
||||
bf.log.warn("On-disk counts were " + levelCount + " levels / " + spans +
|
||||
" spans / " + size + " entries, correcting to " + total + " entries");
|
||||
size = total;
|
||||
flush();
|
||||
}
|
||||
//rng = new Random(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void close() {
|
||||
//System.out.println("Closing index " + size + " and " + spans);
|
||||
flush();
|
||||
spanHash.clear();
|
||||
levelHash.clear();
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
if (!bf.file.canWrite())
|
||||
return;
|
||||
if (isClosed) {
|
||||
bf.log.error("Already closed!! " + this, new Exception());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
BlockFile.pageSeek(bf.file, skipPage);
|
||||
bf.file.writeLong(MAGIC);
|
||||
bf.file.writeInt(firstSpanPage);
|
||||
bf.file.writeInt(firstLevelPage);
|
||||
bf.file.writeInt(Math.max(0, size));
|
||||
bf.file.writeInt(spanHash.size());
|
||||
bf.file.writeInt(levelHash.size());
|
||||
|
||||
} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
|
||||
}
|
||||
|
||||
/** must be open (do not call close() first) */
|
||||
public void delete() throws IOException {
|
||||
if (isClosed) {
|
||||
bf.log.error("Already closed!! " + this, new Exception());
|
||||
return;
|
||||
}
|
||||
SkipLevels curLevel = stack;
|
||||
while(curLevel != null) {
|
||||
SkipLevels nextLevel = curLevel.levels[0];
|
||||
curLevel.killInstance();
|
||||
curLevel = nextLevel;
|
||||
}
|
||||
|
||||
SkipSpan curSpan = first;
|
||||
while(curSpan != null) {
|
||||
SkipSpan nextSpan = curSpan.next;
|
||||
curSpan.killInstance();
|
||||
curSpan = nextSpan;
|
||||
}
|
||||
|
||||
bf.freePage(skipPage);
|
||||
spanHash.clear();
|
||||
levelHash.clear();
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
public static void init(BlockFile bf, int page, int spanSize) throws IOException {
|
||||
int firstSpan = bf.allocPage();
|
||||
int firstLevel = bf.allocPage();
|
||||
BlockFile.pageSeek(bf.file, page);
|
||||
bf.file.writeLong(MAGIC);
|
||||
bf.file.writeInt(firstSpan);
|
||||
bf.file.writeInt(firstLevel);
|
||||
bf.file.writeInt(0);
|
||||
bf.file.writeInt(1);
|
||||
bf.file.writeInt(1);
|
||||
// added in version 1.2
|
||||
bf.file.writeShort(spanSize);
|
||||
BSkipSpan.init(bf, firstSpan, spanSize);
|
||||
BSkipLevels.init(bf, firstLevel, firstSpan, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return log2(span count), minimum 4
|
||||
*/
|
||||
@Override
|
||||
public int maxLevels() {
|
||||
int hob = 0;
|
||||
int s = spanHash.size();
|
||||
while(s > 0) {
|
||||
hob++;
|
||||
s /= P;
|
||||
}
|
||||
int max = Math.max(hob, super.maxLevels());
|
||||
// 252
|
||||
//int cells = (BlockFile.PAGESIZE - BSkipLevels.HEADER_LEN) / 4;
|
||||
return Math.min(BSkipLevels.MAX_SIZE, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkipIterator<K, V> iterator() {
|
||||
if (!this.fileOnly)
|
||||
return super.iterator();
|
||||
return new IBSkipIterator<K, V>(first, 0);
|
||||
}
|
||||
|
||||
/****
|
||||
//@Override
|
||||
public SkipIterator<K, V> min() {
|
||||
return iterator();
|
||||
}
|
||||
|
||||
//@Override
|
||||
public SkipIterator<K, V> max() {
|
||||
if (!this.fileOnly)
|
||||
return super.max();
|
||||
SkipSpan<K, V> ss = stack.getEnd();
|
||||
return new IBSkipIterator<K, V>(ss, ss.nKeys - 1);
|
||||
}
|
||||
****/
|
||||
|
||||
/** find */
|
||||
@Override
|
||||
public SkipIterator<K, V> find(K key) {
|
||||
if (!this.fileOnly)
|
||||
return super.find(key);
|
||||
int[] search = new int[1];
|
||||
SkipSpan<K, V> ss = stack.getSpan(stack.levels.length - 1, key, search);
|
||||
if(search[0] < 0) { search[0] = -1 * (search[0] + 1); }
|
||||
return new IBSkipIterator<K, V>(ss, search[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an integrity check on the skiplist and all the levels in it
|
||||
* @return true if the levels were modified.
|
||||
*/
|
||||
public boolean bslck(boolean fix, boolean isMeta) {
|
||||
bf.log.info(" size " + this.size);
|
||||
bf.log.info(" spans " + this.spanHash.size());
|
||||
bf.log.info(" levels " + this.levelHash.size());
|
||||
bf.log.info(" skipPage " + this.skipPage);
|
||||
bf.log.info(" firstSpanPage " + this.firstSpanPage);
|
||||
bf.log.info(" firstLevelPage " + this.firstLevelPage);
|
||||
bf.log.info(" maxLevels " + this.maxLevels());
|
||||
//printSL();
|
||||
//print();
|
||||
//bf.log.info("*** Lvlck() ***");
|
||||
boolean rv = stack.blvlck(fix);
|
||||
/****
|
||||
int items = 0;
|
||||
for (SkipIterator iter = this.iterator(); iter.hasNext(); ) {
|
||||
String key = (String) iter.nextKey();
|
||||
if (isMeta) {
|
||||
int sz = ((Integer) iter.next()).intValue();
|
||||
bf.log.info(" Item " + key.toString() + " page " + sz);
|
||||
} else {
|
||||
String cls= iter.next().getClass().getSimpleName();
|
||||
bf.log.info(" Item " + key.toString() + " class " + cls);
|
||||
}
|
||||
items++;
|
||||
}
|
||||
bf.log.warn(" actual size " + items);
|
||||
if (items != this.size)
|
||||
bf.log.warn("****** size mismatch, header = " + this.size + " actual = " + items);
|
||||
****/
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String rv = getClass().getSimpleName() + " page " + skipPage;
|
||||
if (isClosed)
|
||||
rv += " CLOSED";
|
||||
return rv;
|
||||
}
|
||||
}
|
@ -1,439 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block.index;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
import net.metanotion.io.block.BlockFile;
|
||||
import net.metanotion.util.skiplist.SkipList;
|
||||
import net.metanotion.util.skiplist.SkipSpan;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* On-disk format:
|
||||
*
|
||||
*<pre>
|
||||
* First Page:
|
||||
* Magic number (int)
|
||||
* overflow page (unsigned int)
|
||||
* previous page (unsigned int)
|
||||
* next page (unsigned int)
|
||||
* max keys (unsigned short)
|
||||
* number of keys (unsigned short)
|
||||
* for each key:
|
||||
* key length (unsigned short)
|
||||
* value length (unsigned short)
|
||||
* key data
|
||||
* value data
|
||||
*
|
||||
* Overflow pages:
|
||||
* Magic number (int)
|
||||
* next overflow page (unsigned int)
|
||||
*</pre>
|
||||
*/
|
||||
public class BSkipSpan<K extends Comparable<? super K>, V> extends SkipSpan<K, V> {
|
||||
protected static final int MAGIC = 0x5370616e; // "Span"
|
||||
protected static final int HEADER_LEN = 20;
|
||||
public static final int CONT_HEADER_LEN = 8;
|
||||
protected final BlockFile bf;
|
||||
private final BSkipList<K, V> bsl;
|
||||
protected int page;
|
||||
protected int overflowPage;
|
||||
|
||||
protected int prevPage;
|
||||
protected int nextPage = 0;
|
||||
protected Serializer<K> keySer;
|
||||
protected Serializer<V> valSer;
|
||||
|
||||
// I2P
|
||||
protected int spanSize;
|
||||
protected boolean isKilled;
|
||||
|
||||
public static void init(BlockFile bf, int page, int spanSize) throws IOException {
|
||||
BlockFile.pageSeek(bf.file, page);
|
||||
bf.file.writeInt(MAGIC);
|
||||
bf.file.writeInt(0);
|
||||
bf.file.writeInt(0);
|
||||
bf.file.writeInt(0);
|
||||
bf.file.writeShort((short) spanSize);
|
||||
bf.file.writeShort(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkipSpan<K, V> newInstance(SkipList<K, V> sl) {
|
||||
try {
|
||||
int newPage = bf.allocPage();
|
||||
init(bf, newPage, bf.spanSize);
|
||||
return new BSkipSpan<K, V>(bf, (BSkipList<K, V>) sl, newPage, keySer, valSer);
|
||||
} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void killInstance() {
|
||||
if (isKilled) {
|
||||
bf.log.error("Already killed!! " + this, new Exception());
|
||||
return;
|
||||
}
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Killing " + this);
|
||||
isKilled = true;
|
||||
try {
|
||||
int curPage = overflowPage;
|
||||
bf.freePage(page);
|
||||
freeContinuationPages(curPage);
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Error freeing " + this, ioe);
|
||||
}
|
||||
bsl.spanHash.remove(Integer.valueOf(this.page));
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a chain of continuation pages
|
||||
* @param curPage the first page to be freed, if 0 this does nothing.
|
||||
* @return number freed
|
||||
*/
|
||||
private int freeContinuationPages(int curPage) throws IOException {
|
||||
int rv = 0;
|
||||
while(curPage > 0) {
|
||||
BlockFile.pageSeek(bf.file, curPage);
|
||||
int magic = bf.file.readInt();
|
||||
if (magic != BlockFile.MAGIC_CONT)
|
||||
throw new IOException("Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + curPage);
|
||||
int next = bf.file.readUnsignedInt();
|
||||
bf.freePage(curPage);
|
||||
curPage = next;
|
||||
rv++;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
fflush();
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P - avoid super.flush()
|
||||
*/
|
||||
private void fflush() {
|
||||
if (isKilled) {
|
||||
bf.log.error("Already killed!! " + this, new Exception());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
BlockFile.pageSeek(bf.file, page);
|
||||
bf.file.writeInt(MAGIC);
|
||||
bf.file.writeInt(overflowPage);
|
||||
prevPage = (prev != null) ? ((BSkipSpan) prev).page : 0;
|
||||
nextPage = (next != null) ? ((BSkipSpan) next).page : 0;
|
||||
bf.file.writeInt(prevPage);
|
||||
bf.file.writeInt(nextPage);
|
||||
// if keys is null, we are (hopefully) just updating the prev/next pages on an unloaded span
|
||||
if (keys == null)
|
||||
return;
|
||||
bf.file.writeShort((short) keys.length);
|
||||
bf.file.writeShort((short) nKeys);
|
||||
if (nKeys <= 0 && prev != null)
|
||||
bf.log.error("Flushing with no entries?" + this, new Exception());
|
||||
|
||||
int curPage = this.page;
|
||||
int[] curNextPage = new int[1];
|
||||
curNextPage[0] = this.overflowPage;
|
||||
int[] pageCounter = new int[1];
|
||||
pageCounter[0] = HEADER_LEN;
|
||||
byte[] keyData;
|
||||
byte[] valData;
|
||||
|
||||
for(int i=0;i<nKeys;i++) {
|
||||
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
||||
if(curNextPage[0] == 0) {
|
||||
curNextPage[0] = bf.allocPage();
|
||||
BlockFile.pageSeek(bf.file, curNextPage[0]);
|
||||
bf.file.writeInt(BlockFile.MAGIC_CONT);
|
||||
bf.file.writeInt(0);
|
||||
BlockFile.pageSeek(bf.file, curPage);
|
||||
bf.file.skipBytes(4); // skip magic
|
||||
bf.file.writeInt(curNextPage[0]);
|
||||
}
|
||||
BlockFile.pageSeek(bf.file, curNextPage[0]);
|
||||
curPage = curNextPage[0];
|
||||
bf.file.skipBytes(4); // skip magic
|
||||
curNextPage[0] = bf.file.readUnsignedInt();
|
||||
pageCounter[0] = CONT_HEADER_LEN;
|
||||
}
|
||||
// Drop bad entry without throwing exception
|
||||
if (keys[i] == null || vals[i] == null) {
|
||||
bf.log.error("Dropping null data in entry " + i + " page " + curPage +
|
||||
" key=" + this.keys[i] + " val=" + this.vals[i]);
|
||||
nKeys--;
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
keyData = this.keySer.getBytes(keys[i]);
|
||||
valData = this.valSer.getBytes(vals[i]);
|
||||
// Drop bad entry without throwing exception
|
||||
if (keyData.length > 65535 || valData.length > 65535) {
|
||||
bf.log.error("Dropping huge data in entry " + i + " page " + curPage +
|
||||
" keylen=" + keyData.length + " vallen=" + valData.length);
|
||||
nKeys--;
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
pageCounter[0] += 4;
|
||||
bf.file.writeShort(keyData.length);
|
||||
bf.file.writeShort(valData.length);
|
||||
curPage = bf.writeMultiPageData(keyData, curPage, pageCounter, curNextPage);
|
||||
curPage = bf.writeMultiPageData(valData, curPage, pageCounter, curNextPage);
|
||||
}
|
||||
BlockFile.pageSeek(bf.file, this.page);
|
||||
bf.file.skipBytes(4); // skip magic
|
||||
this.overflowPage = bf.file.readUnsignedInt();
|
||||
if (curNextPage[0] != 0) {
|
||||
// free extra continuation pages
|
||||
BlockFile.pageSeek(bf.file, curPage);
|
||||
bf.file.skipBytes(4); // skip magic
|
||||
bf.file.writeInt(0);
|
||||
if (curPage == this.page)
|
||||
this.overflowPage = 0;
|
||||
try {
|
||||
int freed = freeContinuationPages(curNextPage[0]);
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Freed " + freed + " continuation pages");
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Error freeing " + this, ioe);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
|
||||
// FIXME can't get there from here
|
||||
//bsl.size -= fail;
|
||||
//bsl.flush();
|
||||
}
|
||||
|
||||
private static <X extends Comparable<? super X>, Y> void load(BSkipSpan<X, Y> bss, BlockFile bf, BSkipList<X, Y> bsl,
|
||||
int spanPage, Serializer<X> key, Serializer<Y> val) throws IOException {
|
||||
loadInit(bss, bf, bsl, spanPage, key, val);
|
||||
bss.loadData();
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P - first half of load()
|
||||
* Only read the span headers
|
||||
*/
|
||||
protected static <X extends Comparable<? super X>, Y> void loadInit(BSkipSpan<X, Y> bss, BlockFile bf, BSkipList<X, Y> bsl,
|
||||
int spanPage, Serializer<X> key, Serializer<Y> val) throws IOException {
|
||||
if (bss.isKilled)
|
||||
throw new IOException("Already killed!! " + bss);
|
||||
bss.page = spanPage;
|
||||
bss.keySer = key;
|
||||
bss.valSer = val;
|
||||
|
||||
bsl.spanHash.put(Integer.valueOf(spanPage), bss);
|
||||
|
||||
BlockFile.pageSeek(bf.file, spanPage);
|
||||
|
||||
int magic = bf.file.readInt();
|
||||
if (magic != MAGIC)
|
||||
throw new IOException("Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + spanPage);
|
||||
bss.overflowPage = bf.file.readUnsignedInt();
|
||||
bss.prevPage = bf.file.readUnsignedInt();
|
||||
bss.nextPage = bf.file.readUnsignedInt();
|
||||
bss.spanSize = bf.file.readUnsignedShort();
|
||||
bss.nKeys = bf.file.readUnsignedShort();
|
||||
if(bss.spanSize < 1 || bss.spanSize > SkipSpan.MAX_SIZE || bss.nKeys > bss.spanSize) {
|
||||
bf.log.error("Invalid span size " + bss.nKeys + " / "+ bss.spanSize);
|
||||
bss.nKeys = 0;
|
||||
bss.spanSize = bf.spanSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P - second half of load()
|
||||
* Load the whole span's keys and values into memory
|
||||
*/
|
||||
protected void loadData() throws IOException {
|
||||
loadData(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P - second half of load()
|
||||
* Load the whole span's keys and values into memory
|
||||
* @param flushOnError set to false if you are going to flush anyway
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void loadData(boolean flushOnError) throws IOException {
|
||||
if (isKilled)
|
||||
throw new IOException("Already killed!! " + this);
|
||||
this.keys = (K[]) new Comparable[this.spanSize];
|
||||
this.vals = (V[]) new Object[this.spanSize];
|
||||
|
||||
int ksz, vsz;
|
||||
int curPage = this.page;
|
||||
int[] curNextPage = new int[1];
|
||||
curNextPage[0] = this.overflowPage;
|
||||
int[] pageCounter = new int[1];
|
||||
pageCounter[0] = HEADER_LEN;
|
||||
// System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage);
|
||||
int fail = 0;
|
||||
for(int i=0;i<this.nKeys;i++) {
|
||||
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
||||
BlockFile.pageSeek(this.bf.file, curNextPage[0]);
|
||||
int magic = bf.file.readInt();
|
||||
if (magic != BlockFile.MAGIC_CONT) {
|
||||
bf.log.error("Lost " + (this.nKeys - i) + " entries - Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage[0]);
|
||||
lostEntries(i, curPage);
|
||||
break;
|
||||
}
|
||||
curPage = curNextPage[0];
|
||||
curNextPage[0] = this.bf.file.readUnsignedInt();
|
||||
pageCounter[0] = CONT_HEADER_LEN;
|
||||
}
|
||||
ksz = this.bf.file.readUnsignedShort();
|
||||
vsz = this.bf.file.readUnsignedShort();
|
||||
pageCounter[0] +=4;
|
||||
byte[] k = new byte[ksz];
|
||||
byte[] v = new byte[vsz];
|
||||
int lastGood = curPage;
|
||||
try {
|
||||
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
|
||||
curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage);
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Lost " + (this.nKeys - i) + " entries - Error loading " + this + " on page " + curPage, ioe);
|
||||
lostEntries(i, lastGood);
|
||||
break;
|
||||
}
|
||||
// System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
|
||||
this.keys[i] = this.keySer.construct(k);
|
||||
this.vals[i] = this.valSer.construct(v);
|
||||
// Drop bad entry without throwing exception
|
||||
if (this.keys[i] == null || this.vals[i] == null) {
|
||||
bf.log.error("Null deserialized data in entry " + i + " page " + curPage +
|
||||
" key=" + this.keys[i] + " val=" + this.vals[i]);
|
||||
fail++;
|
||||
nKeys--;
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// free any excess overflow pages?
|
||||
if (fail > 0) {
|
||||
bf.log.error("Repairing corruption of " + fail + " entries");
|
||||
if (flushOnError)
|
||||
fflush();
|
||||
// FIXME can't get there from here
|
||||
//bsl.size -= fail;
|
||||
//bsl.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to recover from corrupt data in this span.
|
||||
* All entries starting with firstBadEntry are lost.
|
||||
* Zero out the overflow page on lastGoodPage,
|
||||
* and corect the number of entries in the first page.
|
||||
* We don't attempt to free the lost continuation pages.
|
||||
*/
|
||||
protected void lostEntries(int firstBadEntry, int lastGoodPage) {
|
||||
try {
|
||||
this.nKeys = firstBadEntry;
|
||||
// zero overflow page pointer
|
||||
BlockFile.pageSeek(this.bf.file, lastGoodPage);
|
||||
bf.file.skipBytes(4); // skip magic
|
||||
bf.file.writeInt(0);
|
||||
// write new number of keys
|
||||
if (lastGoodPage != this.page) {
|
||||
BlockFile.pageSeek(this.bf.file, this.page);
|
||||
bf.file.skipBytes(18);
|
||||
} else {
|
||||
bf.file.skipBytes(10);
|
||||
}
|
||||
bf.file.writeShort(this.nKeys);
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Error while recovering from corruption of " + this, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
protected BSkipSpan(BlockFile bf, BSkipList<K, V> bsl) {
|
||||
this.bf = bf;
|
||||
this.bsl = bsl;
|
||||
}
|
||||
|
||||
public BSkipSpan(BlockFile bf, BSkipList<K, V> bsl, int spanPage, Serializer<K> key, Serializer<V> val) throws IOException {
|
||||
this.bf = bf;
|
||||
this.bsl = bsl;
|
||||
BSkipSpan.load(this, bf, bsl, spanPage, key, val);
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
|
||||
BSkipSpan<K, V> bss = this;
|
||||
// findbugs ok (set in load() above)
|
||||
int np = nextPage;
|
||||
while(np != 0) {
|
||||
BSkipSpan<K, V> temp = bsl.spanHash.get(Integer.valueOf(np));
|
||||
if(temp != null) {
|
||||
bss.next = temp;
|
||||
break;
|
||||
}
|
||||
bss.next = new BSkipSpan<K, V>(bf, bsl);
|
||||
bss.next.next = null;
|
||||
bss.next.prev = bss;
|
||||
bss = (BSkipSpan<K, V>) bss.next;
|
||||
|
||||
BSkipSpan.load(bss, bf, bsl, np, key, val);
|
||||
np = bss.nextPage;
|
||||
}
|
||||
|
||||
// Go backwards to fill in the rest. This never happens.
|
||||
bss = this;
|
||||
np = prevPage;
|
||||
while(np != 0) {
|
||||
BSkipSpan<K, V> temp = bsl.spanHash.get(Integer.valueOf(np));
|
||||
if(temp != null) {
|
||||
bss.prev = temp;
|
||||
break;
|
||||
}
|
||||
bss.prev = new BSkipSpan<K, V>(bf, bsl);
|
||||
bss.prev.next = bss;
|
||||
bss.prev.prev = null;
|
||||
bss = (BSkipSpan<K, V>) bss.prev;
|
||||
|
||||
BSkipSpan.load(bss, bf, bsl, np, key, val);
|
||||
np = bss.prevPage;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String rv = "BSS page: " + page + " key: \"" + firstKey() + '"';
|
||||
if (isKilled)
|
||||
rv += " KILLED";
|
||||
return rv;
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import net.metanotion.util.skiplist.SkipIterator;
|
||||
import net.metanotion.util.skiplist.SkipSpan;
|
||||
|
||||
/**
|
||||
I2P
|
||||
Overridden to load the span when required and null out the keys and values
|
||||
when the iterator leaves the span.
|
||||
If the caller does not iterate all the way through, the last span
|
||||
will remain in memory.
|
||||
*/
|
||||
public class IBSkipIterator<K extends Comparable<? super K>, V> extends SkipIterator<K, V> {
|
||||
|
||||
public IBSkipIterator(SkipSpan<K, V> ss, int index) {
|
||||
super(ss, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next value, and advances the index
|
||||
* @throws NoSuchElementException
|
||||
* @throws RuntimeException on IOE
|
||||
*/
|
||||
@Override
|
||||
public V next() {
|
||||
V o;
|
||||
if(index < ss.nKeys) {
|
||||
if (ss.vals == null) {
|
||||
try {
|
||||
((IBSkipSpan)ss).seekAndLoadData();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error in iterator", ioe);
|
||||
}
|
||||
}
|
||||
o = ss.vals[index];
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
if(index < (ss.nKeys-1)) {
|
||||
index++;
|
||||
} else if(ss.next != null) {
|
||||
ss.keys = null;
|
||||
ss.vals = null;
|
||||
ss = ss.next;
|
||||
index = 0;
|
||||
} else {
|
||||
ss.keys = null;
|
||||
ss.vals = null;
|
||||
index = ss.nKeys;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key. Does NOT advance the index.
|
||||
* @return the key for which the value will be returned in the subsequent call to next()
|
||||
* @throws NoSuchElementException
|
||||
* @throws RuntimeException on IOE
|
||||
*/
|
||||
@Override
|
||||
public K nextKey() {
|
||||
if(index < ss.nKeys) {
|
||||
if (ss.keys == null) {
|
||||
try {
|
||||
((IBSkipSpan)ss).seekAndLoadData();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error in iterator", ioe);
|
||||
}
|
||||
}
|
||||
return ss.keys[index];
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the previous value, and decrements the index
|
||||
* @throws NoSuchElementException
|
||||
* @throws RuntimeException on IOE
|
||||
*/
|
||||
@Override
|
||||
public V previous() {
|
||||
if(index > 0) {
|
||||
index--;
|
||||
} else if(ss.prev != null) {
|
||||
ss.keys = null;
|
||||
ss.vals = null;
|
||||
ss = ss.prev;
|
||||
if(ss.nKeys <= 0) { throw new NoSuchElementException(); }
|
||||
index = (ss.nKeys - 1);
|
||||
} else {
|
||||
ss.keys = null;
|
||||
ss.vals = null;
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
if (ss.vals == null) {
|
||||
try {
|
||||
((IBSkipSpan)ss).seekAndLoadData();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error in iterator", ioe);
|
||||
}
|
||||
}
|
||||
return ss.vals[index];
|
||||
}
|
||||
}
|
@ -1,409 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.block.index;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
import net.metanotion.io.block.BlockFile;
|
||||
import net.metanotion.util.skiplist.SkipList;
|
||||
import net.metanotion.util.skiplist.SkipSpan;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* I2P version of BSkipSpan
|
||||
*
|
||||
* BSkipSpan stores all keys and values in-memory, backed by the file.
|
||||
* IBSkipSpan stores only the first key, and no values, in-memory.
|
||||
*
|
||||
* For a get(), here we do a linear search through the span in the file
|
||||
* and load only the found value (super() does a binary search in-memory).
|
||||
*
|
||||
* For a put() or remove(), we load all keys and values for the span from
|
||||
* the file, make the modification, flush() out the keys and values,
|
||||
* and null out the keys and values in-memory.
|
||||
*
|
||||
* Recommended span size is 16.
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class IBSkipSpan<K extends Comparable<? super K>, V> extends BSkipSpan<K, V> {
|
||||
|
||||
private K firstKey;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public SkipSpan<K, V> newInstance(SkipList<K, V> sl) {
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Splitting page " + this.page + " containing " + this.nKeys + '/' + this.spanSize);
|
||||
try {
|
||||
int newPage = bf.allocPage();
|
||||
init(bf, newPage, bf.spanSize);
|
||||
SkipSpan<K, V> rv = new IBSkipSpan<K, V>(bf, (BSkipList<K, V>) sl, newPage, keySer, valSer);
|
||||
// this is called after a split, so we need the data arrays initialized
|
||||
rv.keys = (K[]) new Comparable[bf.spanSize];
|
||||
rv.vals = (V[]) new Object[bf.spanSize];
|
||||
return rv;
|
||||
} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush to disk and null out in-memory keys and values, saving only the first key
|
||||
*/
|
||||
@Override
|
||||
public void flush() {
|
||||
super.flush();
|
||||
if (nKeys <= 0)
|
||||
this.firstKey = null;
|
||||
if (keys != null) {
|
||||
if (nKeys > 0)
|
||||
this.firstKey = keys[0];
|
||||
this.keys = null;
|
||||
this.vals = null;
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Flushed data for page " + this.page + " containing " + this.nKeys + '/' + this.spanSize);
|
||||
} else if (bf.log.shouldLog(Log.DEBUG)) {
|
||||
// if keys is null, we are (hopefully) just updating the prev/next pages on an unloaded span
|
||||
bf.log.debug("Flushed pointers for for unloaded page " + this.page + " containing " + this.nKeys + '/' + this.spanSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I2P - second half of load()
|
||||
* Load the whole span's keys and values into memory
|
||||
*/
|
||||
@Override
|
||||
protected void loadData() throws IOException {
|
||||
super.loadData();
|
||||
if (this.nKeys > 0)
|
||||
this.firstKey = this.keys[0];
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Loaded data for page " + this.page + " containing " + this.nKeys + '/' + this.spanSize + " first key: " + this.firstKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must already be seeked to the end of the span header
|
||||
* via loadInit() or seekData()
|
||||
*/
|
||||
private void loadFirstKey() throws IOException {
|
||||
if (this.nKeys <= 0)
|
||||
return;
|
||||
int ksz;
|
||||
int curPage = this.page;
|
||||
int[] curNextPage = new int[1];
|
||||
curNextPage[0] = this.overflowPage;
|
||||
int[] pageCounter = new int[1];
|
||||
pageCounter[0] = HEADER_LEN;
|
||||
ksz = this.bf.file.readUnsignedShort();
|
||||
this.bf.file.skipBytes(2); //vsz
|
||||
pageCounter[0] +=4;
|
||||
byte[] k = new byte[ksz];
|
||||
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
|
||||
this.firstKey = this.keySer.construct(k);
|
||||
if (this.firstKey == null) {
|
||||
bf.log.error("Null deserialized first key in page " + curPage);
|
||||
repair(1);
|
||||
}
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Loaded header for page " + this.page + " containing " + this.nKeys + '/' + this.spanSize + " first key: " + this.firstKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek past the span header
|
||||
*/
|
||||
private void seekData() throws IOException {
|
||||
if (isKilled)
|
||||
throw new IOException("Already killed! " + this);
|
||||
BlockFile.pageSeek(this.bf.file, this.page);
|
||||
int magic = bf.file.readInt();
|
||||
if (magic != MAGIC)
|
||||
throw new IOException("Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + this.page);
|
||||
// 3 ints and 2 shorts
|
||||
this.bf.file.skipBytes(HEADER_LEN - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the start of the span and load the data
|
||||
* Package private so BSkipIterator can call it
|
||||
*/
|
||||
void seekAndLoadData() throws IOException {
|
||||
seekData();
|
||||
loadData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear search through the span in the file for the value.
|
||||
*/
|
||||
private V getData(K key) throws IOException {
|
||||
seekData();
|
||||
int curPage = this.page;
|
||||
int[] curNextPage = new int[1];
|
||||
curNextPage[0] = this.overflowPage;
|
||||
int[] pageCounter = new int[1];
|
||||
pageCounter[0] = HEADER_LEN;
|
||||
int fail = 0;
|
||||
//System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage);
|
||||
for(int i=0;i<this.nKeys;i++) {
|
||||
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
||||
BlockFile.pageSeek(this.bf.file, curNextPage[0]);
|
||||
int magic = bf.file.readInt();
|
||||
if (magic != BlockFile.MAGIC_CONT) {
|
||||
bf.log.error("Lost " + (this.nKeys - i) + " entries - Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage[0]);
|
||||
lostEntries(i, curPage);
|
||||
break;
|
||||
}
|
||||
curPage = curNextPage[0];
|
||||
curNextPage[0] = this.bf.file.readUnsignedInt();
|
||||
pageCounter[0] = CONT_HEADER_LEN;
|
||||
}
|
||||
int ksz = this.bf.file.readUnsignedShort();
|
||||
int vsz = this.bf.file.readUnsignedShort();
|
||||
pageCounter[0] +=4;
|
||||
byte[] k = new byte[ksz];
|
||||
try {
|
||||
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Lost " + (this.nKeys - i) + " entries - Error loading " + this + " on page " + curPage, ioe);
|
||||
lostEntries(i, curPage);
|
||||
break;
|
||||
}
|
||||
//System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
|
||||
K ckey = this.keySer.construct(k);
|
||||
if (ckey == null) {
|
||||
// skip the value and keep going
|
||||
curPage = this.bf.skipMultiPageBytes(vsz, curPage, pageCounter, curNextPage);
|
||||
bf.log.error("Null deserialized key in entry " + i + " page " + curPage);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int diff = ckey.compareTo(key);
|
||||
if (diff == 0) {
|
||||
//System.err.println("Found " + key + " at " + i + " (first: " + this.firstKey + ')');
|
||||
byte[] v = new byte[vsz];
|
||||
try {
|
||||
curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage);
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Lost " + (this.nKeys - i) + " entries - Error loading " + this + " on page " + curPage, ioe);
|
||||
lostEntries(i, curPage);
|
||||
break;
|
||||
}
|
||||
V rv = this.valSer.construct(v);
|
||||
if (rv == null) {
|
||||
bf.log.error("Null deserialized value in entry " + i + " page " + curPage +
|
||||
" key=" + ckey);
|
||||
fail++;
|
||||
}
|
||||
if (fail > 0)
|
||||
repair(fail);
|
||||
return rv;
|
||||
}
|
||||
if (diff > 0) {
|
||||
//System.err.println("NOT Found " + key + " at " + i + " (first: " + this.firstKey + " current: " + ckey + ')');
|
||||
if (fail > 0)
|
||||
repair(fail);
|
||||
return null;
|
||||
}
|
||||
// skip the value and keep going
|
||||
curPage = this.bf.skipMultiPageBytes(vsz, curPage, pageCounter, curNextPage);
|
||||
}
|
||||
//System.err.println("NOT Found " + key + " at end (first: " + this.firstKey + ')');
|
||||
if (fail > 0)
|
||||
repair(fail);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void repair(int fail) {
|
||||
/***** needs work
|
||||
try {
|
||||
loadData(false);
|
||||
if (this.nKeys > 0)
|
||||
this.firstKey = this.keys[0];
|
||||
flush();
|
||||
bf.log.error("Repaired corruption of " + fail + " entries");
|
||||
} catch (IOException ioe) {
|
||||
bf.log.error("Failed to repair corruption of " + fail + " entries", ioe);
|
||||
}
|
||||
*****/
|
||||
}
|
||||
|
||||
private IBSkipSpan(BlockFile bf, BSkipList<K, V> bsl) {
|
||||
super(bf, bsl);
|
||||
}
|
||||
|
||||
public IBSkipSpan(BlockFile bf, BSkipList<K, V> bsl, int spanPage, Serializer<K> key, Serializer<V> val) throws IOException {
|
||||
super(bf, bsl);
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("New ibss page " + spanPage);
|
||||
BSkipSpan.loadInit(this, bf, bsl, spanPage, key, val);
|
||||
loadFirstKey();
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
|
||||
IBSkipSpan<K, V> bss = this;
|
||||
IBSkipSpan<K, V> temp;
|
||||
int np = nextPage;
|
||||
while(np != 0) {
|
||||
temp = (IBSkipSpan<K, V>) bsl.spanHash.get(Integer.valueOf(np));
|
||||
if(temp != null) {
|
||||
bss.next = temp;
|
||||
break;
|
||||
}
|
||||
bss.next = new IBSkipSpan<K, V>(bf, bsl);
|
||||
bss.next.next = null;
|
||||
bss.next.prev = bss;
|
||||
K previousFirstKey = bss.firstKey;
|
||||
bss = (IBSkipSpan<K, V>) bss.next;
|
||||
|
||||
BSkipSpan.loadInit(bss, bf, bsl, np, key, val);
|
||||
bss.loadFirstKey();
|
||||
K nextFirstKey = bss.firstKey;
|
||||
if (previousFirstKey == null || nextFirstKey == null ||
|
||||
previousFirstKey.compareTo(nextFirstKey) >= 0) {
|
||||
// TODO remove, but if we are at the bottom of a level
|
||||
// we have to remove the level too, which is a mess
|
||||
bf.log.error("Corrupt database, span out of order " + ((BSkipSpan)bss.prev).page +
|
||||
" first key " + previousFirstKey +
|
||||
" next page " + bss.page +
|
||||
" first key " + nextFirstKey);
|
||||
}
|
||||
np = bss.nextPage;
|
||||
}
|
||||
|
||||
// Go backwards to fill in the rest. This never happens.
|
||||
bss = this;
|
||||
np = prevPage;
|
||||
while(np != 0) {
|
||||
temp = (IBSkipSpan<K, V>) bsl.spanHash.get(Integer.valueOf(np));
|
||||
if(temp != null) {
|
||||
bss.prev = temp;
|
||||
break;
|
||||
}
|
||||
bss.prev = new IBSkipSpan<K, V>(bf, bsl);
|
||||
bss.prev.next = bss;
|
||||
bss.prev.prev = null;
|
||||
K nextFirstKey = bss.firstKey;
|
||||
bss = (IBSkipSpan<K, V>) bss.prev;
|
||||
|
||||
BSkipSpan.loadInit(bss, bf, bsl, np, key, val);
|
||||
bss.loadFirstKey();
|
||||
K previousFirstKey = bss.firstKey;
|
||||
if (previousFirstKey == null || nextFirstKey == null ||
|
||||
previousFirstKey.compareTo(nextFirstKey) >= 0) {
|
||||
// TODO remove, but if we are at the bottom of a level
|
||||
// we have to remove the level too, which is a mess
|
||||
bf.log.error("Corrupt database, span out of order " + bss.page +
|
||||
" first key " + previousFirstKey +
|
||||
" next page " + ((BSkipSpan)bss.next).page +
|
||||
" first key " + nextFirstKey);
|
||||
}
|
||||
np = bss.prevPage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not call super, we always store first key here
|
||||
*/
|
||||
@Override
|
||||
public K firstKey() {
|
||||
return this.firstKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load whole span from file, do the operation, flush out, then null out in-memory data again.
|
||||
* This is called only via SkipList.find()
|
||||
*/
|
||||
@Override
|
||||
public SkipSpan<K, V> getSpan(K key, int[] search) {
|
||||
try {
|
||||
seekAndLoadData();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error reading database", ioe);
|
||||
}
|
||||
SkipSpan<K, V> rv = super.getSpan(key, search);
|
||||
this.keys = null;
|
||||
this.vals = null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear search if in file, Binary search if in memory
|
||||
*/
|
||||
@Override
|
||||
public V get(K key) {
|
||||
try {
|
||||
if (nKeys == 0) { return null; }
|
||||
if (this.next != null && this.next.firstKey().compareTo(key) <= 0)
|
||||
return next.get(key);
|
||||
return getData(key);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error reading database", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load whole span from file, do the operation, flush out, then null out in-memory data again.
|
||||
*/
|
||||
@Override
|
||||
public SkipSpan<K, V> put(K key, V val, SkipList<K, V> sl) {
|
||||
try {
|
||||
seekAndLoadData();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error reading database", ioe);
|
||||
}
|
||||
SkipSpan<K, V> rv = super.put(key, val, sl);
|
||||
// flush() nulls out the data
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load whole span from file, do the operation, flush out, then null out in-memory data again.
|
||||
*/
|
||||
@Override
|
||||
public Object[] remove(K key, SkipList<K, V> sl) {
|
||||
if (bf.log.shouldLog(Log.DEBUG))
|
||||
bf.log.debug("Remove " + key + " in " + this);
|
||||
if (nKeys <= 0)
|
||||
return null;
|
||||
try {
|
||||
seekAndLoadData();
|
||||
if (this.nKeys == 1 && this.prev == null && this.next != null && this.next.keys == null) {
|
||||
// fix for NPE in SkipSpan if next is not loaded
|
||||
if (bf.log.shouldLog(Log.INFO))
|
||||
bf.log.info("Loading next data for remove");
|
||||
((IBSkipSpan)this.next).seekAndLoadData();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Error reading database attempting to remove " + key, ioe);
|
||||
}
|
||||
Object[] rv = super.remove(key, sl);
|
||||
// flush() nulls out the data
|
||||
return rv;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.data;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
|
||||
/**
|
||||
* May be used to scan and repair the database nondestructively.
|
||||
* Will never return null.
|
||||
* Added by I2P.
|
||||
*/
|
||||
public class IdentityBytes implements Serializer<byte[]> {
|
||||
|
||||
/** @return byte[] */
|
||||
public byte[] getBytes(byte[] o) { return o; }
|
||||
|
||||
/** @return b */
|
||||
public byte[] construct(byte[] b) { return b; }
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.data;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
|
||||
public class IntBytes implements Serializer<Integer> {
|
||||
public byte[] getBytes(Integer o) {
|
||||
byte[] b = new byte[4];
|
||||
int v = o.intValue();
|
||||
b[0] = (byte)(0xff & (v >> 24));
|
||||
b[1] = (byte)(0xff & (v >> 16));
|
||||
b[2] = (byte)(0xff & (v >> 8));
|
||||
b[3] = (byte)(0xff & v);
|
||||
return b;
|
||||
}
|
||||
|
||||
public Integer construct(byte[] b) {
|
||||
int v = (((b[0] & 0xff) << 24) |
|
||||
((b[1] & 0xff) << 16) |
|
||||
((b[2] & 0xff) << 8) |
|
||||
(b[3] & 0xff));
|
||||
return Integer.valueOf(v);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.data;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
|
||||
public class StringBytes implements Serializer<String> {
|
||||
public byte[] getBytes(String o) {
|
||||
try {
|
||||
return o.getBytes("US-ASCII");
|
||||
} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
|
||||
}
|
||||
|
||||
public String construct(byte[] b) {
|
||||
try {
|
||||
return new String(b, "US-ASCII");
|
||||
} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.io.data;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import net.metanotion.io.Serializer;
|
||||
|
||||
/**
|
||||
* Added by I2P
|
||||
*/
|
||||
public class UTF8StringBytes implements Serializer<String> {
|
||||
public byte[] getBytes(String o) {
|
||||
try {
|
||||
return o.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
|
||||
}
|
||||
|
||||
public String construct(byte[] b) {
|
||||
try {
|
||||
return new String(b, "UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link rel="home" title="Home" href="http://www.metanotion.net/software/sandbox/" />
|
||||
|
||||
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
|
||||
<meta name="robots" content="all" />
|
||||
|
||||
<title>BlockFile</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
I2P Notes:
|
||||
This is the database used by the BlockfileNamingService class.
|
||||
It is heavily modified from the original 0.1.1 version.
|
||||
Not for direct use by apps, clients, or plugins.
|
||||
This package is not currently intended for general use, as
|
||||
the API may be subject to change.
|
||||
Contact I2P developers if you are considering use in another application.
|
||||
Following is the original documentation copied from metanotion website.
|
||||
</p>
|
||||
<h1>Metanotion BlockFile Database</h1>
|
||||
<p>A 100% Java 1.3, BSD Licensed, embeddable single file database engine in 32KB. This database was designed for PDA based and J2ME applications.</p>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#unfeatures">Unfeatures</a></li>
|
||||
<li><a href="#future">Future Plans</a></li>
|
||||
<li><a href="#design">What kind of database is this?</a></li>
|
||||
<li><a href="#examples">Examples and API</a></li>
|
||||
<li><a href="#download">Download</a></li>
|
||||
</ul>
|
||||
|
||||
<a name="features"><h2>Features</h2></a>
|
||||
<ul>
|
||||
<li>100% Java 1.3. No JNI.</li>
|
||||
<li>Will work with any "file" as long as you can approximate something like <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/RandomAccessFile.html">java.io.RandomAccessFile</a>, you can use this.</li>
|
||||
<li>BSD Licensed. Yes, this means you can use it for free in a commercial project. However, if you base some really cool mobile technology startup on this code we'll gladly accept stock options...</p>
|
||||
<li>No dependence on file API's(useful for mobile apps)</li>
|
||||
<li>Small. 32KB in a JAR file. <2000 lines of code.</li>
|
||||
<li>Reasonably fast. This is used in an app running on a sub 200MHz StrongARM PocketPC, and quite handily deals with 70,000 records. The load time is a little slow, but its been tested with a <a href="http://java.sun.com/javame/reference/apis.jsp">CDC 1.0/Personal Profile</a> device.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a name="unfeatures"><h2>Unfeatures</h2></a>
|
||||
<p>A good, ACID database is a nice thing to work with. Unfortunately, in the goal to make this small, fast, and work with minimal dependencies, something had to give. So I list things which this database will likely never have. Of course, since it is BSD Licensed, patches welcome...</p>
|
||||
|
||||
<ul>
|
||||
<li>No transactions.</li>
|
||||
<li>No SQL.</li>
|
||||
<li>No JDBC.</li>
|
||||
<li>No use of reflection or automagical serialization tricks.</li>
|
||||
</ul>
|
||||
|
||||
<a name="future"><h2>Future Plans</h2></a>
|
||||
<p>There are still bugs(none known...). The app that this was written for is still in testing, but we should most of the issues sorted by the time we deploy it in a few weeks(early November, 2006). Some loading speed issues on large record sets, and memory usage could still be improved. All this and feedback from other uses will direct this products evolution.</p>
|
||||
<p>What is currently up here is not "1.0" code, but we will release a labeled "1.0" version once we feel happy with the state of the codebase.</p>
|
||||
|
||||
<a name="design"><h2>What KIND of database is this?</h2></a>
|
||||
<p>You probably store at least part of your application data in memory in a class from the <a href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/">Java Collections Framework</a>. The BlockFile database stores data in a <a href="http://en.wikipedia.org/wiki/Skip_list">Skip</a> <a href="http://eternallyconfuzzled.com/tuts/skip.html">List</a> that almost implements <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/SortedMap.html">java.util.SortedMap</a>. You can create and store as many named(with a string) SkipList in the database as you want.</p>
|
||||
<p>To serialize your data, you have to either extend our SerialStreams class or implement our Serializer interface. We could have done something cool and fancy with reflection(and other cool stuff with Java 1.5), but that would probably not do the Right Thing™ most of the time. As you can see, there's not a lot to it anyway:</p>
|
||||
<h3>net.metanotion.io.SerialStreams</h3>
|
||||
<pre>
|
||||
public abstract class SerialStreams implements Serializer {
|
||||
// ...
|
||||
abstract public void writeOut(DataOutputStream dos, Object o) throws IOException;
|
||||
abstract public Object readIn(DataInputStream dis) throws IOException;
|
||||
}
|
||||
</pre>
|
||||
<h3>net.metanotion.io.Serializer</h3>
|
||||
<pre>
|
||||
public interface Serializer {
|
||||
public byte[] getBytes(Object o);
|
||||
public Object construct(byte[] b);
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>Now, about those skip lists. They implement a <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/ListIterator.html">java.util.ListIterator</a> so you can get "nearby" values, and you can use anything for a key that implements <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Comparable.html">java.lang.Comparable</a>. So, here's the interface to a SkipList:
|
||||
<pre>
|
||||
public class SkipList {
|
||||
...
|
||||
public void put(Comparable key, Object val) ...
|
||||
public Object remove(Comparable key) ...
|
||||
public Object get(Comparable key) ...
|
||||
public ListIterator iterator() ...
|
||||
public ListIterator min() ...
|
||||
public ListIterator max() ...
|
||||
// Find the first key bigger than or equal to key,
|
||||
// or the biggest key less than key if there is no bigger or equal.
|
||||
public ListIterator find(Comparable key) ...
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<a name="examples"><h2>Examples</h2></a>
|
||||
<p>Better documentation is forthcoming, but there really isn't much to know. The entire public interface to the library is on this page. Where possible, it sticks to idiomatic Java and standard interfaces.</p>
|
||||
<ul>
|
||||
<li>Open a database:
|
||||
<pre>
|
||||
import net.metanotion.io.block.BlockFile;
|
||||
...
|
||||
try {
|
||||
BlockFile db = new BlockFile(new File("my.db"), false); // true will create
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Bummer");
|
||||
}
|
||||
</pre>
|
||||
<li>
|
||||
<li>Load or Create a SkipList:
|
||||
<pre>
|
||||
import net.metanotion.util.skiplist.SkipList;
|
||||
import net.metanotion.io.Serializer;
|
||||
...
|
||||
class KeySerializer implements Serializer ...
|
||||
class ValueSerializer implements Serializer ...
|
||||
...
|
||||
// Open preexisting
|
||||
SkipList index = db.getIndex("My Index", new KeySerializer(), new ValueSerializer());
|
||||
// Create
|
||||
SkipList index = db.makeIndex("My Index", new KeySerializer(), new ValueSerializer());
|
||||
</pre>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>net.metanotion.io.block.BlockFile</h3>
|
||||
All the public interface methods:
|
||||
<pre>
|
||||
public class BlockFile implements Closeable {
|
||||
public BlockFile(RandomAccessInterface rai) ...
|
||||
public BlockFile(RandomAccessFile raf) ...
|
||||
public BlockFile(RandomAccessFile raf, boolean init) ...
|
||||
public BlockFile(File f, boolean init) ...
|
||||
public BlockFile(RandomAccessInterface rai, boolean init) ...
|
||||
|
||||
public SkipList getIndex(String name, Serializer key, Serializer val) ...
|
||||
public SkipList makeIndex(String name, Serializer key, Serializer val) ...
|
||||
public void delIndex(String name) ...
|
||||
|
||||
public void close() ...
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h3>What's this "net.metanotion.io.RandomAccessInterface"?</h3>
|
||||
<p>Basically, its an interface version of <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/RandomAccessFile.html">java.io.RandomAccessFile</a>(which itself implements <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/DataInput.html">DataInput</a>, <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/DataOutput.html">DataOutput</a> and a few methods for getting/setting the file pointer).</p>
|
||||
|
||||
<p>So, in other words, if you can provide an implementation of this interface, you can use the BlockFile database. This frees it from dependence on the RandomAccessFile class. If you don't see why this is useful and you're going to be using "files" on PDA's and phone's, well, you'll understand soon enough...</p>
|
||||
|
||||
<a name="download"><h2>Download</h2></a>
|
||||
<h3>Bugfix and cleanup Release 10/6/2006</h2>
|
||||
<p>An unnecessary class was removed, some junk methods removed, and a couple of JDK compatability issues were fixed. The StringBytes class was switched to ASCII(from UTF-8) for better compatibility.</p>
|
||||
<ul>
|
||||
<li><a href="http://www.metanotion.net/software/sandbox/BlockFile.2006.10.06.jar">BlockFile binary JAR, version 0.1.1</a></li>
|
||||
<li><a href="http://www.metanotion.net/software/sandbox/BlockFile.src.2006.10.06.zip">BlockFile source code</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Initial Release 9/28/2006</h3>
|
||||
<ul>
|
||||
<li><a href="http://www.metanotion.net/software/sandbox/BlockFile.2006.09.28.jar">BlockFile binary JAR, version 0.1</a></li>
|
||||
<li><a href="http://www.metanotion.net/software/sandbox/BlockFile.src.2006.09.28.zip">BlockFile source code</a></li>
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
<center>© 2006 <a href="http://www.metanotion.net/">Metanotion Software</a></center>
|
||||
</body>
|
||||
</html>
|
@ -1,121 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.util.skiplist;
|
||||
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** A basic iterator for a skip list.
|
||||
This is not a complete ListIterator, in particular, since the
|
||||
skip list is a map and is therefore indexed by Comparable objects instead
|
||||
of int's, the nextIndex and previousIndex methods are not really relevant.
|
||||
|
||||
To be clear, this is an iterator through the values.
|
||||
To get the key, call nextKey() BEFORE calling next().
|
||||
*/
|
||||
public class SkipIterator<K extends Comparable<? super K>, V> implements ListIterator<V> {
|
||||
protected SkipSpan<K, V> ss;
|
||||
protected int index;
|
||||
|
||||
protected SkipIterator() { }
|
||||
|
||||
public SkipIterator(SkipSpan<K, V> ss, int index) {
|
||||
if(ss==null) { throw new NullPointerException(); }
|
||||
this.ss = ss;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
if(index < ss.nKeys) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next value, and advances the index
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
public V next() {
|
||||
V o;
|
||||
if(index < ss.nKeys) {
|
||||
o = ss.vals[index];
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
if(index < (ss.nKeys-1)) {
|
||||
index++;
|
||||
} else if(ss.next != null) {
|
||||
ss = ss.next;
|
||||
index = 0;
|
||||
} else {
|
||||
index = ss.nKeys;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key. Does NOT advance the index.
|
||||
* @return the key for which the value will be returned in the subsequent call to next()
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
public K nextKey() {
|
||||
if(index < ss.nKeys) { return ss.keys[index]; }
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
public boolean hasPrevious() {
|
||||
if(index > 0) { return true; }
|
||||
if((ss.prev != null) && (ss.prev.nKeys > 0)) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the previous value, and decrements the index
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
public V previous() {
|
||||
if(index > 0) {
|
||||
index--;
|
||||
} else if(ss.prev != null) {
|
||||
ss = ss.prev;
|
||||
if(ss.nKeys <= 0) { throw new NoSuchElementException(); }
|
||||
index = (ss.nKeys - 1);
|
||||
}
|
||||
return ss.vals[index];
|
||||
}
|
||||
|
||||
|
||||
// Optional methods
|
||||
public void add(V o) { throw new UnsupportedOperationException(); }
|
||||
public void remove() { throw new UnsupportedOperationException(); }
|
||||
public void set(V o) { throw new UnsupportedOperationException(); }
|
||||
public int nextIndex() { throw new UnsupportedOperationException(); }
|
||||
public int previousIndex() { throw new UnsupportedOperationException(); }
|
||||
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.util.skiplist;
|
||||
|
||||
import java.io.Flushable;
|
||||
|
||||
import net.metanotion.io.block.BlockFile;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class SkipLevels<K extends Comparable<? super K>, V> implements Flushable {
|
||||
/** We can't have more than 2**32 pages */
|
||||
public static final int MAX_SIZE = 32;
|
||||
|
||||
/*
|
||||
* "Next" pointers
|
||||
* The highest indexed level is the "highest" level in the list.
|
||||
* The "bottom" level is the direct pointer to a SkipSpan.
|
||||
*/
|
||||
// levels is almost final
|
||||
public SkipLevels<K, V>[] levels;
|
||||
// bottom is final
|
||||
public SkipSpan<K, V> bottom;
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
|
||||
|
||||
public SkipLevels<K, V> newInstance(int levels, SkipSpan<K, V> ss, SkipList<K, V> sl) {
|
||||
return new SkipLevels<K, V>(levels, ss);
|
||||
}
|
||||
|
||||
public void killInstance() { }
|
||||
public void flush() { }
|
||||
|
||||
protected SkipLevels() { }
|
||||
|
||||
/*
|
||||
* @throws IllegalArgumentException if size too big or too small
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public SkipLevels(int size, SkipSpan<K, V> span) {
|
||||
if(size < 1 || size > MAX_SIZE)
|
||||
throw new IllegalArgumentException("Invalid Level Skip size");
|
||||
levels = (SkipLevels<K, V>[]) new SkipLevels[size];
|
||||
bottom = span;
|
||||
}
|
||||
|
||||
public String print() {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
String k = (bottom.nKeys == 0) ? "empty" : (key() != null) ? key().toString() : "null";
|
||||
buf.append("LVLS: ").append(k).append(" :: ");
|
||||
for(int i=0;i<levels.length;i++) {
|
||||
buf.append(i);
|
||||
if(levels[i] != null) {
|
||||
buf.append("->").append(levels[i].key()).append(' ');
|
||||
} else {
|
||||
buf.append("->() ");
|
||||
}
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String printAll() {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
buf.append(print());
|
||||
if(levels[0] != null) {
|
||||
buf.append('\n');
|
||||
buf.append(levels[0].print());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public SkipSpan<K, V> getEnd() {
|
||||
for(int i=(levels.length - 1);i>=0;i--) {
|
||||
if(levels[i] != null) { return levels[i].getEnd(); }
|
||||
}
|
||||
return bottom.getEnd();
|
||||
}
|
||||
|
||||
public SkipSpan<K, V> getSpan(int start, K key, int[] search) {
|
||||
for(int i=Math.min(start, levels.length - 1);i>=0;i--) {
|
||||
if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
|
||||
return levels[i].getSpan(i,key,search);
|
||||
}
|
||||
}
|
||||
return bottom.getSpan(key, search);
|
||||
}
|
||||
|
||||
public K key() { return bottom.firstKey(); }
|
||||
|
||||
public V get(int start, K key) {
|
||||
for(int i=Math.min(start, levels.length - 1);i>=0;i--) {
|
||||
if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
|
||||
return levels[i].get(i,key);
|
||||
}
|
||||
}
|
||||
return bottom.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An array of two objects or null.
|
||||
* rv[0] is the removed object.
|
||||
* rv[1] is the deleted SkipLevels if the removed object was the last in the SkipLevels,
|
||||
* and the deleted SkipLevels is taller than this SkipLevels.
|
||||
* rv is null if no object was removed.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object[] remove(int start, K key, SkipList<K, V> sl) {
|
||||
Object[] res = null;
|
||||
SkipLevels<K, V> slvls = null;
|
||||
for(int i = Math.min(start, levels.length - 1); i >= 0; i--) {
|
||||
if(levels[i] != null) {
|
||||
int cmp = levels[i].key().compareTo(key);
|
||||
if((cmp < 0) || ((i==0) && (cmp <= 0))) {
|
||||
res = levels[i].remove(i, key, sl);
|
||||
if((res != null) && (res[1] != null)) {
|
||||
slvls = (SkipLevels<K, V>) res[1];
|
||||
if(levels.length >= slvls.levels.length) {
|
||||
res[1] = null;
|
||||
}
|
||||
for(int j = 0 ; j < Math.min(slvls.levels.length, levels.length); j++) {
|
||||
if(levels[j] == slvls) {
|
||||
levels[j] = slvls.levels[j];
|
||||
}
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
res = bottom.remove(key, sl);
|
||||
if((res!=null) && (res[1] != null)) {
|
||||
if(res[1] == bottom) {
|
||||
res[1] = this;
|
||||
} else {
|
||||
// Special handling required if we are the head SkipLevels to fix up our level pointers
|
||||
// if the returned SkipSpan was already copied to us
|
||||
boolean isFirst = sl.first == bottom;
|
||||
if (isFirst && levels[0] != null) {
|
||||
SkipSpan<K, V> ssres = (SkipSpan<K, V>)res[1];
|
||||
if (bottom.firstKey().equals(ssres.firstKey())) {
|
||||
// bottom copied the next span to itself
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("First Level, bottom.remove() copied and did not return itself!!!! in remove " + key);
|
||||
_log.info("Us: " + print());
|
||||
_log.info("next: " + levels[0].print());
|
||||
_log.info("ssres.firstKey(): " + ssres.firstKey());
|
||||
_log.info("ssres.keys[0]: " + ssres.keys[0]);
|
||||
_log.info("FIXUP TIME");
|
||||
}
|
||||
|
||||
SkipLevels<K, V> replace = levels[0];
|
||||
for (int i = 0; i < levels.length; i++) {
|
||||
if (levels[i] == null)
|
||||
break;
|
||||
if (i >= replace.levels.length)
|
||||
break;
|
||||
if (levels[i].key().equals(replace.key())) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("equal level " + i);
|
||||
levels[i] = replace.levels[i];
|
||||
} else if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("not equal level " + i + ' ' + levels[i].key());
|
||||
}
|
||||
}
|
||||
this.flush();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("new Us: " + print());
|
||||
replace.killInstance();
|
||||
}
|
||||
}
|
||||
res[1] = null;
|
||||
}
|
||||
}
|
||||
if((bottom.nKeys == 0) && (sl.first != bottom)) {
|
||||
// from debugging other problems
|
||||
if (res == null) {
|
||||
_log.warn("killing with no return value " + print());
|
||||
} else if (res[1] == null) {
|
||||
_log.warn("killing with no return value 1 " + print());
|
||||
} else if (res[1] != this) {
|
||||
_log.warn("killing with return value not us " + res[1] + ' ' + print());
|
||||
}
|
||||
this.killInstance();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the new level if it caused a split and we made a new level,
|
||||
* and the new level is taller than our level;
|
||||
* else null if it went in an existing level or the new level is our height or less.
|
||||
*/
|
||||
public SkipLevels<K, V> put(int start, K key, V val, SkipList<K, V> sl) {
|
||||
boolean modified = false;
|
||||
for(int i = Math.min(start, levels.length - 1); i >= 0; i--) {
|
||||
// is key equal to or after the start of the level?
|
||||
if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
|
||||
SkipLevels<K, V> slvls = levels[i].put(i, key, val, sl);
|
||||
if(slvls != null) {
|
||||
for (int j = i + 1; j < Math.min(slvls.levels.length, levels.length); j++) {
|
||||
// he points to where we used to point
|
||||
// and we now point to him
|
||||
slvls.levels[j] = levels[j];
|
||||
levels[j] = slvls;
|
||||
modified = true;
|
||||
}
|
||||
if(levels.length < slvls.levels.length) {
|
||||
if (modified) {
|
||||
this.flush();
|
||||
slvls.flush();
|
||||
}
|
||||
return slvls;
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
this.flush();
|
||||
if (slvls != null)
|
||||
slvls.flush();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
SkipSpan<K, V> ss = bottom.put(key,val,sl);
|
||||
if(ss!=null) {
|
||||
int height = sl.generateColHeight();
|
||||
if(height != 0) {
|
||||
SkipLevels<K, V> slvls = this.newInstance(height, ss, sl);
|
||||
for(int i=0;i<(Math.min(height,levels.length));i++) {
|
||||
// he points to where we used to point
|
||||
// and we now point to him
|
||||
slvls.levels[i] = levels[i];
|
||||
levels[i] = slvls;
|
||||
modified = true;
|
||||
}
|
||||
if (modified) {
|
||||
this.flush();
|
||||
slvls.flush();
|
||||
}
|
||||
if(levels.length < height)
|
||||
return slvls;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean blvlck(boolean fix) { return false; }
|
||||
public boolean blvlck(boolean fix, int width, SkipLevels<K, V>[] prevLevels) { return false; }
|
||||
}
|
||||
|
@ -1,357 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.util.skiplist;
|
||||
|
||||
import java.io.Flushable;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
//import net.metanotion.io.block.BlockFile;
|
||||
|
||||
public class SkipList<K extends Comparable<? super K>, V> implements Flushable, Iterable<V> {
|
||||
/** the probability of each next higher level */
|
||||
protected static final int P = 2;
|
||||
private static final int MIN_SLOTS = 4;
|
||||
// these two are really final
|
||||
protected SkipSpan<K, V> first;
|
||||
protected SkipLevels<K, V> stack;
|
||||
// I2P mod
|
||||
public static final Random rng = RandomSource.getInstance();
|
||||
|
||||
protected int size;
|
||||
|
||||
public void flush() { }
|
||||
protected SkipList() { }
|
||||
|
||||
/*
|
||||
* @param span span size
|
||||
* @throws IllegalArgumentException if size too big or too small
|
||||
*/
|
||||
public SkipList(int span) {
|
||||
if(span < 1 || span > SkipSpan.MAX_SIZE)
|
||||
throw new IllegalArgumentException("Invalid span size");
|
||||
first = new SkipSpan<K, V>(span);
|
||||
stack = new SkipLevels<K, V>(1, first);
|
||||
//rng = new Random(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
|
||||
public void addItem() {
|
||||
size++;
|
||||
}
|
||||
|
||||
public void delItem() {
|
||||
if (size > 0)
|
||||
size--;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 4 since we don't track span count here any more - see override
|
||||
* Fix if for some reason you want a huge in-memory skiplist.
|
||||
*/
|
||||
public int maxLevels() {
|
||||
return MIN_SLOTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0..maxLevels(), each successive one with probability 1 / P
|
||||
*/
|
||||
public int generateColHeight() {
|
||||
int bits = rng.nextInt();
|
||||
int max = maxLevels();
|
||||
for(int res = 0; res < max; res++) {
|
||||
if (bits % P == 0)
|
||||
return res;
|
||||
bits /= P;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void put(K key, V val) {
|
||||
if(key == null) { throw new NullPointerException(); }
|
||||
if(val == null) { throw new NullPointerException(); }
|
||||
SkipLevels<K, V> slvls = stack.put(stack.levels.length - 1, key, val, this);
|
||||
if(slvls != null) {
|
||||
// grow our stack
|
||||
//BlockFile.log.info("Top level old hgt " + stack.levels.length + " new hgt " + slvls.levels.length);
|
||||
SkipLevels<K, V>[] levels = (SkipLevels<K, V>[]) new SkipLevels[slvls.levels.length];
|
||||
for(int i=0;i < slvls.levels.length; i++) {
|
||||
if(i < stack.levels.length) {
|
||||
levels[i] = stack.levels[i];
|
||||
} else {
|
||||
levels[i] = slvls;
|
||||
}
|
||||
}
|
||||
stack.levels = levels;
|
||||
stack.flush();
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public V remove(K key) {
|
||||
if(key == null) { throw new NullPointerException(); }
|
||||
Object[] res = stack.remove(stack.levels.length - 1, key, this);
|
||||
if(res != null) {
|
||||
if(res[1] != null) {
|
||||
SkipLevels<K, V> slvls = (SkipLevels<K, V>) res[1];
|
||||
for(int i=0;i < slvls.levels.length; i++) {
|
||||
if(stack.levels[i] == slvls) {
|
||||
stack.levels[i] = slvls.levels[i];
|
||||
}
|
||||
}
|
||||
stack.flush();
|
||||
}
|
||||
flush();
|
||||
return (V) res[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* dumps all the skip levels
|
||||
* @deprecated goes to System.out
|
||||
*/
|
||||
@Deprecated
|
||||
public void printSL() {
|
||||
System.out.println("List size " + size);
|
||||
System.out.println(stack.printAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* dumps all the data
|
||||
* @deprecated goes to System.out
|
||||
*/
|
||||
@Deprecated
|
||||
public void print() {
|
||||
System.out.println("List size " + size);
|
||||
System.out.println(first.print());
|
||||
}
|
||||
|
||||
public V get(K key) {
|
||||
if(key == null) { throw new NullPointerException(); }
|
||||
return stack.get(stack.levels.length - 1, key);
|
||||
}
|
||||
|
||||
public SkipIterator<K, V> iterator() { return new SkipIterator<K, V>(first, 0); }
|
||||
|
||||
/****
|
||||
public SkipIterator<K, V> min() { return new SkipIterator<K, V>(first, 0); }
|
||||
|
||||
public SkipIterator<K, V> max() {
|
||||
SkipSpan<K, V> ss = stack.getEnd();
|
||||
return new SkipIterator<K, V>(ss, ss.nKeys - 1);
|
||||
}
|
||||
****/
|
||||
|
||||
/** @return an iterator where nextKey() is the first one greater than or equal to 'key' */
|
||||
public SkipIterator<K, V> find(K key) {
|
||||
int[] search = new int[1];
|
||||
SkipSpan<K, V> ss = stack.getSpan(stack.levels.length - 1, key, search);
|
||||
if(search[0] < 0) { search[0] = -1 * (search[0] + 1); }
|
||||
return new SkipIterator<K, V>(ss, search[0]);
|
||||
}
|
||||
|
||||
// Levels adjusted to guarantee O(log n) search
|
||||
// This is expensive proportional to the number of spans.
|
||||
public void balance() {
|
||||
// TODO Skip List Balancing Algorithm
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Basic Error generating conditions to test
|
||||
insert into empty
|
||||
insert into non empty
|
||||
remove from empty
|
||||
remove from non-empty a non-existant key
|
||||
get from empty
|
||||
get from non-empty a non-existant key
|
||||
|
||||
Repeat, with splits induced, and collapse induced.
|
||||
*/
|
||||
/*****
|
||||
public static void main(String args[]) {
|
||||
SkipList sl = new SkipList(3);
|
||||
sl.put(".1", "1");
|
||||
sl.remove("2");
|
||||
sl.remove("1");
|
||||
sl.put(".1", "1-1");
|
||||
sl.put(".2", "2");
|
||||
sl.put(".3", "3");
|
||||
*****/
|
||||
/* System.out.println("\n#1");
|
||||
sl.print();
|
||||
*/
|
||||
/*****
|
||||
|
||||
sl.put(".4", "4");
|
||||
*****/
|
||||
/* System.out.println("\n#2");
|
||||
sl.print();
|
||||
|
||||
sl.remove("1");
|
||||
System.out.println("\n#2.1");
|
||||
sl.print();
|
||||
sl.remove("2");
|
||||
System.out.println("\n#2.2");
|
||||
sl.print();
|
||||
sl.remove("3");
|
||||
System.out.println("\n#2.3");
|
||||
sl.print();
|
||||
sl.remove("4");
|
||||
|
||||
System.out.println("\n#3");
|
||||
sl.print();
|
||||
*/
|
||||
/******
|
||||
sl.put(".1", "1-2");
|
||||
sl.put(".2", "2-1");
|
||||
sl.put(".3", "3-1");
|
||||
sl.put(".4", "4-1");
|
||||
// System.out.println("\n#4");
|
||||
// sl.print();
|
||||
sl.put(".5", "5-1");
|
||||
sl.put(".6", "6-1");
|
||||
sl.put(".7", "7-1");
|
||||
|
||||
// System.out.println("\n#5");
|
||||
// sl.print();
|
||||
|
||||
// sl.remove("5");
|
||||
sl.put(".5", "5-2");
|
||||
// System.out.println("\n#6");
|
||||
// sl.print();
|
||||
|
||||
sl.put(".8", "8");
|
||||
sl.put(".9", "9");
|
||||
sl.put("10", "10");
|
||||
sl.put("11", "11");
|
||||
sl.put("12", "12");
|
||||
sl.put("13", "13");
|
||||
sl.put("14", "14");
|
||||
sl.put("15", "15");
|
||||
sl.put("16", "16");
|
||||
sl.put("17", "17");
|
||||
sl.put("18", "18");
|
||||
sl.put("19", "19");
|
||||
sl.put("20", "20");
|
||||
sl.put("21", "21");
|
||||
sl.put("22", "22");
|
||||
sl.put("23", "23");
|
||||
sl.put("24", "24");
|
||||
sl.put("25", "25");
|
||||
sl.put("26", "26");
|
||||
sl.put("27", "27");
|
||||
sl.put("28", "28");
|
||||
sl.put("29", "29");
|
||||
sl.put("30", "30");
|
||||
sl.put("31", "31");
|
||||
sl.put("32", "32");
|
||||
sl.put("33", "33");
|
||||
sl.put("34", "34");
|
||||
sl.put("35", "35");
|
||||
sl.put("36", "36");
|
||||
sl.put("37", "37");
|
||||
sl.put("38", "38");
|
||||
sl.put("39", "39");
|
||||
sl.put("40", "40");
|
||||
|
||||
// System.out.println("\n#7");
|
||||
// sl.print();
|
||||
System.out.println("GET " + sl.get("10"));
|
||||
System.out.println("GET " + sl.get("12"));
|
||||
System.out.println("GET " + sl.get("32"));
|
||||
System.out.println("GET " + sl.get("33"));
|
||||
System.out.println("GET " + sl.get("37"));
|
||||
System.out.println("GET " + sl.get("40"));
|
||||
|
||||
sl.printSL();
|
||||
|
||||
sl.remove("33");
|
||||
sl.printSL();
|
||||
sl.remove("34");
|
||||
sl.printSL();
|
||||
sl.remove("36");
|
||||
sl.printSL();
|
||||
sl.remove("35");
|
||||
sl.printSL();
|
||||
|
||||
// System.out.println("\n#8");
|
||||
sl.print();
|
||||
System.out.println("GET " + sl.get("10"));
|
||||
System.out.println("GET " + sl.get("12"));
|
||||
System.out.println("GET " + sl.get("32"));
|
||||
System.out.println("GET " + sl.get("33"));
|
||||
System.out.println("GET " + sl.get("37"));
|
||||
System.out.println("GET " + sl.get("40"));
|
||||
|
||||
System.out.println("Height " + sl.stack.levels.length);
|
||||
|
||||
SkipIterator si = sl.iterator();
|
||||
for(int i=0;i<5;i++) {
|
||||
System.out.println("Iterator: " + si.next());
|
||||
}
|
||||
for(int i=0;i<3;i++) {
|
||||
System.out.println("Iterator: " + si.previous());
|
||||
}
|
||||
|
||||
System.out.println("Find 10");
|
||||
si = sl.find("10");
|
||||
for(int i=0;i<5;i++) {
|
||||
System.out.println("Iterator: " + si.next());
|
||||
}
|
||||
for(int i=0;i<3;i++) {
|
||||
System.out.println("Iterator: " + si.previous());
|
||||
}
|
||||
|
||||
System.out.println("Find 34");
|
||||
si = sl.find("34");
|
||||
for(int i=0;i<3;i++) {
|
||||
System.out.println("Iterator: " + si.previous());
|
||||
}
|
||||
for(int i=0;i<5;i++) {
|
||||
System.out.println("Iterator: " + si.next());
|
||||
}
|
||||
|
||||
System.out.println("Max");
|
||||
si = sl.max();
|
||||
for(int i=0;i<3;i++) {
|
||||
System.out.println("Iterator: " + si.previous());
|
||||
}
|
||||
for(int i=0;i<5;i++) {
|
||||
System.out.println("Iterator: " + si.next());
|
||||
}
|
||||
}
|
||||
*****/
|
||||
}
|
@ -1,318 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Metanotion Software nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.metanotion.util.skiplist;
|
||||
|
||||
import java.io.Flushable;
|
||||
|
||||
//import net.metanotion.io.block.BlockFile;
|
||||
|
||||
public class SkipSpan<K extends Comparable<? super K>, V> implements Flushable {
|
||||
/** This is actually limited by BlockFile.spanSize which is much smaller */
|
||||
public static final int MAX_SIZE = 256;
|
||||
|
||||
public int nKeys = 0;
|
||||
public K[] keys;
|
||||
public V[] vals;
|
||||
public SkipSpan<K, V> next, prev;
|
||||
|
||||
public SkipSpan<K, V> newInstance(SkipList<K, V> sl) { return new SkipSpan<K, V>(keys.length); }
|
||||
public void killInstance() { }
|
||||
public void flush() { }
|
||||
|
||||
protected SkipSpan() { }
|
||||
|
||||
/*
|
||||
* @throws IllegalArgumentException if size too big or too small
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public SkipSpan(int size) {
|
||||
if(size < 1 || size > MAX_SIZE)
|
||||
throw new IllegalArgumentException("Invalid span size " + size);
|
||||
keys = (K[]) new Comparable[size];
|
||||
vals = (V[]) new Object[size];
|
||||
}
|
||||
|
||||
/** dumps all the data from here to the end */
|
||||
public String print() {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("Span with ").append(nKeys).append(" keys\n");
|
||||
if (nKeys > 0 && keys != null && vals != null) {
|
||||
for(int i=0;i<nKeys;i++) {
|
||||
buf.append('\t').append(keys[i]).append(" => ").append(vals[i]).append('\n');
|
||||
}
|
||||
}
|
||||
if (next != null) { buf.append(next.print()); }
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private int binarySearch(K key) {
|
||||
int high = nKeys - 1;
|
||||
int low = 0;
|
||||
int cur;
|
||||
int cmp;
|
||||
while(low <= high) {
|
||||
cur = (low + high) >>> 1;
|
||||
cmp = keys[cur].compareTo(key);
|
||||
if(cmp > 0) {
|
||||
high = cur - 1;
|
||||
} else if(cmp < 0) {
|
||||
low = cur + 1;
|
||||
} else {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return (-1 * (low + 1));
|
||||
}
|
||||
|
||||
public SkipSpan<K, V> getEnd() {
|
||||
if(next == null) { return this; }
|
||||
return next.getEnd();
|
||||
}
|
||||
|
||||
public SkipSpan<K, V> getSpan(K key, int[] search) {
|
||||
if(nKeys == 0) {
|
||||
search[0] = -1;
|
||||
return this;
|
||||
}
|
||||
|
||||
if(keys[nKeys - 1].compareTo(key) < 0) {
|
||||
if(next == null) {
|
||||
search[0] = (-1 * (nKeys - 1)) - 1;
|
||||
return this;
|
||||
}
|
||||
return next.getSpan(key, search);
|
||||
}
|
||||
search[0] = binarySearch(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public V get(K key) {
|
||||
if(nKeys == 0) { return null; }
|
||||
if(keys[nKeys - 1].compareTo(key) < 0) {
|
||||
if(next == null) { return null; }
|
||||
return next.get(key);
|
||||
}
|
||||
int loc = binarySearch(key);
|
||||
if(loc < 0) { return null; }
|
||||
return vals[loc];
|
||||
}
|
||||
|
||||
private void pushTogether(int hole) {
|
||||
for(int i=hole;i<(nKeys - 1);i++) {
|
||||
keys[i] = keys[i+1];
|
||||
vals[i] = vals[i+1];
|
||||
}
|
||||
nKeys--;
|
||||
}
|
||||
|
||||
private void pushApart(int start) {
|
||||
for(int i=(nKeys-1);i>=start;i--) {
|
||||
keys[i+1] = keys[i];
|
||||
vals[i+1] = vals[i];
|
||||
}
|
||||
nKeys++;
|
||||
}
|
||||
|
||||
private void split(int loc, K key, V val, SkipList<K, V> sl) {
|
||||
SkipSpan<K, V> right = newInstance(sl);
|
||||
|
||||
if(this.next != null) { this.next.prev = right; }
|
||||
right.next = this.next;
|
||||
right.prev = this;
|
||||
this.next = right;
|
||||
|
||||
int start = ((keys.length+1)/2);
|
||||
for(int i=start;i < keys.length; i++) {
|
||||
try {
|
||||
right.keys[i-start] = keys[i];
|
||||
right.vals[i-start] = vals[i];
|
||||
right.nKeys++;
|
||||
this.nKeys--;
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
System.out.println("i " + i + " start " + start);
|
||||
System.out.println("key: " + keys[i].toString());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if(loc >= start) {
|
||||
right.pushApart(loc - start);
|
||||
right.keys[loc - start] = key;
|
||||
right.vals[loc - start] = val;
|
||||
} else {
|
||||
pushApart(loc);
|
||||
keys[loc] = key;
|
||||
vals[loc] = val;
|
||||
}
|
||||
this.flush();
|
||||
this.next.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the new span if it caused a split, else null if it went in this span
|
||||
*/
|
||||
private SkipSpan<K, V> insert(int loc, K key, V val, SkipList<K, V> sl) {
|
||||
sl.addItem();
|
||||
if(nKeys == keys.length) {
|
||||
// split.
|
||||
split(loc, key, val, sl);
|
||||
return next;
|
||||
} else {
|
||||
pushApart(loc);
|
||||
keys[loc] = key;
|
||||
vals[loc] = val;
|
||||
this.flush();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the new span if it caused a split, else null if it went in an existing span
|
||||
*/
|
||||
public SkipSpan<K, V> put(K key, V val, SkipList<K, V> sl) {
|
||||
if(nKeys == 0) {
|
||||
sl.addItem();
|
||||
keys[0] = key;
|
||||
vals[0] = val;
|
||||
nKeys++;
|
||||
this.flush();
|
||||
return null;
|
||||
}
|
||||
int loc = binarySearch(key);
|
||||
if(loc < 0) {
|
||||
loc = -1 * (loc + 1);
|
||||
if(next != null) {
|
||||
int cmp = next.firstKey().compareTo(key);
|
||||
if((loc >= nKeys) && (cmp > 0)) {
|
||||
// It fits in between this span and the next
|
||||
// Try to avoid a split...
|
||||
if(nKeys == keys.length) {
|
||||
if(next.nKeys == keys.length) {
|
||||
return insert(loc, key, val, sl);
|
||||
} else {
|
||||
return next.put(key, val, sl);
|
||||
}
|
||||
} else {
|
||||
return insert(loc, key, val, sl);
|
||||
}
|
||||
} else {
|
||||
// Its either clearly in the next span or this span.
|
||||
if(cmp > 0) {
|
||||
return insert(loc, key, val, sl);
|
||||
} else {
|
||||
return next.put(key, val, sl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no next span, So
|
||||
// either it goes here, or causes a split.
|
||||
return insert(loc, key, val, sl);
|
||||
}
|
||||
} else {
|
||||
// Key already exists. Overwrite value.
|
||||
vals[loc] = val;
|
||||
this.flush();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An array of two objects or null.
|
||||
* rv[0] is the removed object.
|
||||
* rv[1] is the deleted SkipSpan if the removed object was the last in the SkipSpan.
|
||||
* rv is null if no object was removed.
|
||||
*/
|
||||
public Object[] remove(K key, SkipList<K, V> sl) {
|
||||
if(nKeys == 0) { return null; }
|
||||
if(keys[nKeys - 1].compareTo(key) < 0) {
|
||||
if(next == null) { return null; }
|
||||
return next.remove(key, sl);
|
||||
}
|
||||
int loc = binarySearch(key);
|
||||
if(loc < 0) { return null; }
|
||||
Object o = vals[loc];
|
||||
Object[] res = new Object[2];
|
||||
res[0] = o;
|
||||
sl.delItem();
|
||||
if(nKeys == 1) {
|
||||
if((this.prev == null) && (this.next != null)) {
|
||||
res[1] = this.next;
|
||||
// We're the first node in the list... copy the next node over and kill it. See also bottom of SkipLevels.java
|
||||
for(int i=0;i<next.nKeys;i++) {
|
||||
keys[i] = next.keys[i];
|
||||
vals[i] = next.vals[i];
|
||||
}
|
||||
|
||||
nKeys = next.nKeys;
|
||||
//BlockFile.log.error("Killing next span " + next + ") and copying to this span " + this + " in remove of " + key);
|
||||
// Make us point to next.next and him point back to us
|
||||
SkipSpan<K, V> nn = next.next;
|
||||
next.killInstance();
|
||||
if (nn != null) {
|
||||
nn.prev = this;
|
||||
nn.flush();
|
||||
}
|
||||
this.next = nn;
|
||||
this.flush();
|
||||
} else {
|
||||
// Normal situation. We are now empty, kill ourselves
|
||||
//BlockFile.log.error("Killing this span " + this + ", prev " + this.prev + ", next " + this.next);
|
||||
if(this.prev != null) {
|
||||
this.prev.next = this.next;
|
||||
this.prev.flush();
|
||||
}
|
||||
if(this.next != null) {
|
||||
this.next.prev = this.prev;
|
||||
this.next.flush();
|
||||
this.next = null;
|
||||
}
|
||||
if (this.prev != null) {
|
||||
// Kill ourselves
|
||||
this.prev = null;
|
||||
this.killInstance();
|
||||
res[1] = this;
|
||||
} else {
|
||||
// Never kill first span
|
||||
//BlockFile.log.error("Not killing First span, now empty!!!!!!!!!!!!!!!!!!");
|
||||
this.flush();
|
||||
res[1] = null;
|
||||
}
|
||||
nKeys = 0;
|
||||
}
|
||||
} else {
|
||||
pushTogether(loc);
|
||||
this.flush();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** I2P */
|
||||
public K firstKey() {
|
||||
return keys[0];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user