I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • equincey/i2p.i2p
  • marek/i2p.i2p
  • kytv/i2p.i2p
  • agentoocat/i2p.i2p
  • aargh/i2p.i2p
  • Kalhintz/i2p.i2p
  • longyap/i2p.i2p
  • kelare/i2p.i2p
  • apsoyka/i2p.i2p
  • mesh/i2p.i2p
  • ashtod/i2p.i2p
  • y2kboy23/i2p.i2p
  • Lfrr/i2p.i2p
  • anonymousmaybe/i2p.i2p
  • obscuratus/i2p.i2p
  • zzz/i2p.i2p
  • lbt/i2p.i2p
  • 31337/i2p.i2p
  • DuncanIdaho/i2p.i2p
  • loveisgrief/i2p.i2p
  • i2p-hackers/i2p.i2p
  • thebland/i2p.i2p
  • elde/i2p.i2p
  • echelon/i2p.i2p
  • welshlyluvah1967/i2p.i2p
  • zlatinb/i2p.i2p
  • sadie/i2p.i2p
  • pVT0/i2p.i2p
  • idk/i2p.i2p
29 results
Show changes
Showing
with 3445 additions and 33 deletions
/*
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;
}
}
/*
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;
}
}
/*
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;
}
}
/*
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;
}
}
/*
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];
}
}
/*
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;
}
}
/*
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; }
}
/*
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);
}
}
/*
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"); }
}
}
/*
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"); }
}
}
<!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>
<h2>Metanotion BlockFile Database</h2>
<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>
<h2 id="features">Features</h2>
<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...</li>
<li>No dependence on file API's(useful for mobile apps)</li>
<li>Small. 32KB in a JAR file. &lt;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>
<h2 id="unfeatures">Unfeatures</h2>
<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>
<h2 id="future">Future Plans</h2>
<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>
<h2 id="design">What KIND of database is this?</h2>
<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&trade; 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>
<h2 id="examples">Examples</h2>
<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>
<h2 id="download">Download</h2>
<h3>Bugfix and cleanup Release 10/6/2006</h3>
<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>
<p>&copy; 2006 <a href="http://www.metanotion.net/">Metanotion Software</a></p>
</body>
</html>
/*
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(); }
}
/*
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; }
}
/*
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());
}
}
*****/
}
/*
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];
}
}
package net.i2p.router.naming;
import junit.framework.TestCase;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
public class BlockfileNamingServiceTest extends TestCase {
BlockfileNamingService _bns;
List<String> _names;
File hostsTxt, routerDir;
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setUp() throws Exception {
I2PAppContext ctx = new I2PAppContext();
routerDir = ctx.getRouterDir();
// first load the list of hosts that will be queried
InputStream is = getClass().getResourceAsStream("/hosts.txt");
Properties props = new Properties();
assertNotNull("test classpath not set correctly",is);
DataHelper.loadProps(props, is, true);
_names = new ArrayList<String>((Set<String>) (Set) props.keySet()); // TODO-Java6: s/keySet()/stringPropertyNames()/
Collections.shuffle(_names);
is.close();
// then copy the hosts.txt file so that the naming service can load them
hostsTxt = new File(routerDir, "hosts.txt");
OutputStream os = new BufferedOutputStream(new FileOutputStream(hostsTxt));
is = getClass().getResourceAsStream("/hosts.txt");
byte [] b = new byte[8196];
int read = 0;
while ((read = is.read(b)) > 0 )
os.write(b,0,read);
os.flush(); os.close();
_bns = new BlockfileNamingService(ctx);
}
public void tearDown() {
_bns.shutdown();
if (routerDir != null) {
File f = new File(routerDir,"hostsdb.blockfile");
f.delete();
}
if (hostsTxt != null)
hostsTxt.delete();
}
public void testRepeatedLookup() throws Exception{
int found = 0;
int notfound = 0;
int rfound = 0;
int rnotfound = 0;
for (String name : _names) {
Destination dest = _bns.lookup(name);
if (dest != null) {
found++;
String reverse = _bns.reverseLookup(dest);
if (reverse != null)
rfound++;
else
rnotfound++;
} else {
notfound++;
}
}
assertEquals(0, notfound);
assertEquals(0, rnotfound);
}
}
# addressbook master address book. Addresses placed in this file take precidence
# over those in the router address book and in remote address books. If changes
# are made to this file, they will be reflected in the router address book and
# published address book after the next update.
#
# Do not make changes directly to the router address book, as they could be lost
# during an update.
#
# This file takes addresses in the hosts.txt format, i.e.
# example.i2p=somereallylongbase64thingAAAA
# Subscription list for addressbook
#
# Each entry is an absolute url to a file in hosts.txt format.
# Since the list is checked in order, url's should be listed in order of trust.
#
http://dev.i2p/i2p/hosts.txt
http://duck.i2p/hosts.txt
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
<servlet>
<servlet-name>addressbook</servlet-name>
<servlet-class>addressbook.Servlet</servlet-class>
<init-param>
<param-name>home</param-name>
<param-value>./addressbook</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
</web-app>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/java/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="core" />
<orderEntry type="module" module-name="router" />
<orderEntry type="module" module-name="routerconsole" />
</component>
</module>
\ No newline at end of file