propagate from branch 'i2p.i2p' (head ca5b19055e887994435b0eb35978484f2489bb6e)

to branch 'i2p.i2p.zzz.naming' (head e71d7dc813c07bb2b6798ab74099efdfc1754f47)
This commit is contained in:
zzz
2011-03-10 17:59:35 +00:00
42 changed files with 4673 additions and 969 deletions

View File

@ -72,6 +72,10 @@ Public domain except as listed below:
Contains some code Copyright 2006 Sun Microsystems, Inc.
See licenses/LICENSE-InstallCert.txt
BlockFile:
Copyright (c) 2006, Matthew Estes
See licenses/LICENSE-BlockFile.txt
Router:
Public domain except as listed below:

View File

@ -257,7 +257,7 @@
splitindex="true"
doctitle="I2P Javadocs for Release ${release.number} Build ${build.number}"
windowtitle="I2P Anonymous Network - Java Documentation - Version ${release.number}">
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:org.bouncycastle.crypto:org.bouncycastle.crypto.*:gnu.crypto.*:gnu.gettext:org.xlattice.crypto.filters:com.nettgryppa.security" />
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:org.bouncycastle.crypto:org.bouncycastle.crypto.*:gnu.crypto.*:gnu.gettext:org.xlattice.crypto.filters:com.nettgryppa.security:net.metanotion.*" />
<group title="Streaming Library" packages="net.i2p.client.streaming" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:org.cybergarage.*:org.freenetproject" />
<group title="Router Console" packages="net.i2p.router.web" />

View File

@ -7,7 +7,6 @@ import java.util.Random;
import java.util.Set;
import net.i2p.client.naming.NamingService;
import net.i2p.client.naming.PetNameDB;
import net.i2p.crypto.AESEngine;
import net.i2p.crypto.CryptixAESEngine;
import net.i2p.crypto.DSAEngine;
@ -72,7 +71,6 @@ public class I2PAppContext {
private StatManager _statManager;
private SessionKeyManager _sessionKeyManager;
private NamingService _namingService;
private PetNameDB _petnameDb;
private ElGamalEngine _elGamalEngine;
private ElGamalAESEngine _elGamalAESEngine;
private AESEngine _AESEngine;
@ -89,7 +87,6 @@ public class I2PAppContext {
private volatile boolean _statManagerInitialized;
private volatile boolean _sessionKeyManagerInitialized;
private volatile boolean _namingServiceInitialized;
private volatile boolean _petnameDbInitialized;
private volatile boolean _elGamalEngineInitialized;
private volatile boolean _elGamalAESEngineInitialized;
private volatile boolean _AESEngineInitialized;
@ -177,7 +174,6 @@ public class I2PAppContext {
_statManager = null;
_sessionKeyManager = null;
_namingService = null;
_petnameDb = null;
_elGamalEngine = null;
_elGamalAESEngine = null;
_logManager = null;
@ -580,23 +576,6 @@ public class I2PAppContext {
}
}
/** @deprecated unused */
public PetNameDB petnameDb() {
if (!_petnameDbInitialized)
initializePetnameDb();
return _petnameDb;
}
/** @deprecated unused */
private void initializePetnameDb() {
synchronized (this) {
if (_petnameDb == null) {
_petnameDb = new PetNameDB();
}
_petnameDbInitialized = true;
}
}
/**
* This is the ElGamal engine used within this context. While it doesn't
* really have anything substantial that is context specific (the algorithm

View File

@ -1,62 +0,0 @@
package net.i2p.client.naming;
import java.lang.reflect.Constructor;
import java.util.Collection;
import net.i2p.I2PAppContext;
import net.i2p.data.Address;
import net.i2p.util.Log;
/**
* @deprecated unused
*/
public abstract class AddressDB {
private final static Log _log = new Log(NamingService.class);
protected I2PAppContext _context;
/** what classname should be used as the address db impl? */
public static final String PROP_IMPL = "i2p.addressdb.impl";
private static final String DEFAULT_IMPL = "net.i2p.client.naming.FilesystemAddressDB";
/**
* The address db should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected AddressDB(I2PAppContext context) {
_context = context;
}
private AddressDB() { // nop
}
/**
* Get an address db instance. This method ensures that there
* will be only one address db instance (singleton) as well as
* choose the implementation from the "i2p.addressdb.impl" system
* property.
*/
public static final synchronized AddressDB createInstance(I2PAppContext context) {
AddressDB instance = null;
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
try {
Class cls = Class.forName(impl);
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
instance = (AddressDB)con.newInstance(new Object[] { context });
} catch (Exception ex) {
_log.error("Cannot load address db implementation", ex);
instance = new DummyAddressDB(context); // fallback
}
return instance;
}
public abstract Address get(String hostname);
public abstract Address put(Address address);
public abstract Address remove(String hostname);
public abstract Address remove(Address address);
public abstract boolean contains(Address address);
public abstract boolean contains(String hostname);
public abstract Collection hostnames();
}

View File

@ -1,47 +0,0 @@
package net.i2p.client.naming;
import java.util.Iterator;
import net.i2p.I2PAppContext;
import net.i2p.data.Address;
import net.i2p.data.Destination;
/**
* @deprecated unused
*/
public class AddressDBNamingService extends NamingService {
private AddressDB _addressdb;
public AddressDBNamingService(I2PAppContext context) {
super(context);
_addressdb = AddressDB.createInstance(context);
}
private AddressDBNamingService() {
super(null);
}
@Override
public Destination lookup(String hostname) {
Address addr = _addressdb.get(hostname);
if (addr != null) {
return addr.getDestination();
} else {
// If we can't find hostname in the addressdb, assume it's a key.
return lookupBase64(hostname);
}
}
@Override
public String reverseLookup(Destination dest) {
Iterator iter = _addressdb.hostnames().iterator();
while (iter.hasNext()) {
Address addr = _addressdb.get((String)iter.next());
if (addr != null && addr.getDestination().equals(dest)) {
return addr.getHostname();
}
}
return null;
}
}

View File

@ -0,0 +1,741 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
import net.metanotion.io.Serializer;
import net.metanotion.io.block.BlockFile;
import net.metanotion.util.skiplist.SkipIterator;
import net.metanotion.util.skiplist.SkipList;
/**
* A naming service using the net.metanotion BlockFile database.
*
* This database contains the following skiplists:
* <pre>
*
* "%%__INFO__%%" is the master database skiplist, containing one entry:
* "info": a Properties, serialized with DataHelper functions:
* "version": "1"
* "created": Java long time (ms)
* "lists": Comma-separated list of host databases, to be
* searched in-order for lookups
*
*
* For each host database, there is a skiplist containing
* the hosts for that database.
* The keys/values in these skiplists are as follows:
* key: a UTF-8 String
* value: a DestEntry, which is a Properties (serialized with DataHelper)
* followed by a Destination (serialized as usual).
*
*
* The DestEntry Properties typically contains:
* "a": The time added (Java long time in ms)
* "s": The original source of the entry (typically a file name or subscription URL)
* others TBD
*
* </pre>
*
*/
public class BlockfileNamingService extends DummyNamingService {
private final Log _log;
private final BlockFile _bf;
private final RandomAccessFile _raf;
private final List<String> _lists;
private volatile boolean _isClosed;
private static final Serializer _infoSerializer = new PropertiesSerializer();
private static final Serializer _stringSerializer = new StringSerializer();
private static final Serializer _destSerializer = new DestEntrySerializer();
private static final String HOSTS_DB = "hostsdb.blockfile";
private static final String PROP_HOSTS_FILE = "i2p.hostsfilelist";
private static final String PROP_B32 = "i2p.naming.hostsTxt.useB32";
private static final String DEFAULT_HOSTS_FILE =
"privatehosts.txt,userhosts.txt,hosts.txt";
private static final String FALLBACK_LIST = "hosts.txt";
private static final String INFO_SKIPLIST = "%%__INFO__%%";
private static final String PROP_INFO = "info";
private static final String PROP_VERSION = "version";
private static final String PROP_LISTS = "lists";
private static final String PROP_CREATED = "created";
private static final String PROP_MODIFIED = "modified";
private static final String VERSION = "1";
private static final String PROP_ADDED = "a";
private static final String PROP_SOURCE = "s";
/**
* @throws RuntimeException on fatal error
*/
public BlockfileNamingService(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(BlockfileNamingService.class);
_lists = new ArrayList();
BlockFile bf = null;
RandomAccessFile raf = null;
File f = new File(_context.getRouterDir(), HOSTS_DB);
if (f.exists()) {
try {
// closing a BlockFile does not close the underlying file,
// so we must create and retain a RAF so we may close it later
raf = new RandomAccessFile(f, "rw");
bf = initExisting(raf);
} catch (IOException ioe) {
if (raf != null) {
try { raf.close(); } catch (IOException e) {}
}
File corrupt = new File(_context.getRouterDir(), HOSTS_DB + ".corrupt");
_log.log(Log.CRIT, "Corrupt or unreadable database " + f + ", moving to " + corrupt +
" and creating new database", ioe);
boolean success = f.renameTo(corrupt);
if (!success)
_log.log(Log.CRIT, "Failed to move corrupt database " + f + " to " + corrupt);
}
}
if (bf == null) {
try {
// closing a BlockFile does not close the underlying file,
// so we must create and retain a RAF so we may close it later
raf = new RandomAccessFile(f, "rw");
bf = init(raf);
} catch (IOException ioe) {
if (raf != null) {
try { raf.close(); } catch (IOException e) {}
}
_log.log(Log.CRIT, "Failed to initialize database", ioe);
throw new RuntimeException(ioe);
}
}
_bf = bf;
_raf = raf;
_context.addShutdownTask(new Shutdown());
}
/**
* Create a new database and initialize it from the local files
* privatehosts.txt, userhosts.txt, and hosts.txt,
* creating a skiplist in the database for each.
*/
private BlockFile init(RandomAccessFile f) throws IOException {
long start = _context.clock().now();
try {
BlockFile rv = new BlockFile(f, true);
SkipList hdr = rv.makeIndex(INFO_SKIPLIST, _stringSerializer, _infoSerializer);
Properties info = new Properties();
info.setProperty(PROP_VERSION, VERSION);
info.setProperty(PROP_CREATED, Long.toString(_context.clock().now()));
String list = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
info.setProperty(PROP_LISTS, list);
hdr.put(PROP_INFO, info);
// TODO all in one skiplist or separate?
int total = 0;
for (String hostsfile : getFilenames(list)) {
File file = new File(_context.getRouterDir(), hostsfile);
if ((!file.exists()) || !(file.canRead()))
continue;
int count = 0;
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), 16*1024);
String line = null;
while ( (line = in.readLine()) != null) {
if (line.startsWith("#"))
continue;
int split = line.indexOf('=');
if (split <= 0)
continue;
String key = line.substring(0, split).toLowerCase();
if (line.indexOf('#') > 0) { // trim off any end of line comment
line = line.substring(0, line.indexOf('#')).trim();
if (line.length() < split + 1)
continue;
}
String b64 = line.substring(split+1); //.trim() ??????????????
Destination d = lookupBase64(b64);
if (d != null) {
addEntry(rv, hostsfile, key, d, hostsfile);
count++;
}
}
} catch (IOException ioe) {
_log.error("Failed to read hosts from " + file, ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
total += count;
_log.logAlways(Log.INFO, "Added " + count + " hosts from " + file);
_lists.add(hostsfile);
}
_log.error("DB init took " + DataHelper.formatDuration(_context.clock().now() - start));
if (total <= 0)
_log.error("Warning - initialized database with zero entries");
return rv;
} catch (RuntimeException e) {
throw new IOException(e.toString());
}
}
/**
* Read the info block of an existing database.
*/
private BlockFile initExisting(RandomAccessFile raf) throws IOException {
long start = _context.clock().now();
try {
BlockFile bf = new BlockFile(raf, false);
// TODO all in one skiplist or separate?
SkipList hdr = bf.getIndex(INFO_SKIPLIST, _stringSerializer, _infoSerializer);
if (hdr == null)
throw new IOException("No db header");
Properties info = (Properties) hdr.get(PROP_INFO);
if (info == null)
throw new IOException("No header info");
String version = info.getProperty(PROP_VERSION);
if (!VERSION.equals(version))
throw new IOException("Bad db version: " + version);
String list = info.getProperty(PROP_LISTS);
if (list == null)
throw new IOException("No lists");
long createdOn = 0;
String created = info.getProperty(PROP_CREATED);
if (created != null) {
try {
createdOn = Long.parseLong(created);
} catch (NumberFormatException nfe) {}
}
_log.error("Found database version " + version + " created " + (new Date(createdOn)).toString() +
" containing lists: " + list);
List<String> skiplists = getFilenames(list);
if (skiplists.isEmpty())
skiplists.add(FALLBACK_LIST);
_lists.addAll(skiplists);
_log.error("DB init took " + DataHelper.formatDuration(_context.clock().now() - start));
return bf;
} catch (RuntimeException e) {
throw new IOException(e.toString());
}
}
/**
* Caller must synchronize
* @return entry or null, or throws ioe
*/
private DestEntry getEntry(String listname, String key) throws IOException {
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null)
return null;
DestEntry rv = (DestEntry) sl.get(key);
// Control memory usage
//////// _bf.closeIndex(listname);
return rv;
} catch (IOException ioe) {
_log.error("DB Lookup error", ioe);
// delete index??
throw ioe;
} catch (RuntimeException e) {
_log.error("DB Lookup error", e);
throw new IOException(e.toString());
}
}
/**
* Caller must synchronize
* @param source may be null
*/
private void addEntry(BlockFile bf, String listname, String key, Destination dest, String source) throws IOException {
try {
// catch IOE and delete index??
SkipList sl = bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null) {
//_log.info("Making new skiplist " + listname);
sl = bf.makeIndex(listname, _stringSerializer, _destSerializer);
}
Properties props = new Properties();
props.setProperty(PROP_ADDED, Long.toString(_context.clock().now()));
if (source != null)
props.setProperty(PROP_SOURCE, source);
addEntry(sl, key, dest, props);
// Control memory usage
////// bf.closeIndex(listname);
} catch (IOException ioe) {
_log.error("DB add error", ioe);
// delete index??
throw ioe;
} catch (RuntimeException e) {
_log.error("DB add error", e);
throw new IOException(e.toString());
}
}
/**
* Caller must synchronize
* @param source may be null
* @throws RuntimeException
*/
private void addEntry(SkipList sl, String key, Destination dest, String source) {
Properties props = new Properties();
props.setProperty(PROP_ADDED, Long.toString(_context.clock().now()));
if (source != null)
props.setProperty(PROP_SOURCE, source);
addEntry(sl, key, dest, props);
}
/**
* Caller must synchronize
* @param props may be null
* @throws RuntimeException
*/
private static void addEntry(SkipList sl, String key, Destination dest, Properties props) {
DestEntry de = new DestEntry();
de.dest = dest;
de.props = props;
sl.put(key, de);
}
private static List<String> getFilenames(String list) {
StringTokenizer tok = new StringTokenizer(list, ",");
List<String> rv = new ArrayList(tok.countTokens());
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
return rv;
}
/**
* Caller must synchronize
* @return removed object or null
* @throws RuntimeException
*/
private static Object removeEntry(SkipList sl, String key) {
return sl.remove(key);
}
////////// Start NamingService API
@Override
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
Destination d = super.lookup(hostname);
if (d != null)
return d;
String key = hostname.toLowerCase();
synchronized(_bf) {
if (_isClosed)
return null;
for (String list : _lists) {
try {
DestEntry de = getEntry(list, key);
if (de != null) {
d = de.dest;
if (storedOptions != null)
storedOptions.putAll(de.props);
break;
}
} catch (IOException ioe) {
break;
}
}
}
if (d != null)
putCache(hostname, d);
return d;
}
/**
* @param options If non-null and contains the key "list", add to that list
* (default "hosts.txt")
* Use the key "s" for the source
*/
@Override
public boolean put(String hostname, Destination d, Properties options) {
return put(hostname, d, options, false);
}
/**
* @param options If non-null and contains the key "list", add to that list
* (default "hosts.txt")
* Use the key "s" for the source
*/
@Override
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
return put(hostname, d, options, true);
}
private boolean put(String hostname, Destination d, Properties options, boolean checkExisting) {
String key = hostname.toLowerCase();
String listname = FALLBACK_LIST;
Properties props = new Properties();
if (options != null) {
props.putAll(options);
String list = options.getProperty("list");
if (list != null) {
listname = list;
props.remove("list");
}
}
props.setProperty(PROP_ADDED, Long.toString(_context.clock().now()));
synchronized(_bf) {
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null)
sl = _bf.makeIndex(listname, _stringSerializer, _destSerializer);
boolean changed = (checkExisting || !_listeners.isEmpty()) && sl.get(key) != null;
if (changed && checkExisting)
return false;
addEntry(sl, key, d, props);
for (NamingServiceListener nsl : _listeners) {
if (changed)
nsl.entryChanged(this, hostname, d, options);
else
nsl.entryAdded(this, hostname, d, options);
}
return true;
} catch (IOException re) {
return false;
} catch (RuntimeException re) {
return false;
}
}
}
/**
* @param options If non-null and contains the key "list", remove
* from that list (default "hosts.txt", NOT all lists)
*/
@Override
public boolean remove(String hostname, Properties options) {
String key = hostname.toLowerCase();
String listname = FALLBACK_LIST;
if (options != null) {
String list = options.getProperty("list");
if (list != null) {
listname = list;
}
}
synchronized(_bf) {
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null)
return false;
boolean rv = removeEntry(sl, key) != null;
if (rv) {
for (NamingServiceListener nsl : _listeners) {
nsl.entryRemoved(this, key);
}
}
return rv;
} catch (IOException re) {
return false;
} catch (RuntimeException re) {
return false;
}
}
}
/**
* @param options If non-null and contains the key "list", get
* from that list (default "hosts.txt", NOT all lists)
* Key "limit": max number to return
* Key "startsWith": return only those starting with
* Key "beginWith": start here in the iteration
* Don't use both
*/
@Override
public Map<String, Destination> getEntries(Properties options) {
String listname = FALLBACK_LIST;
String startsWith = null;
String beginWith = null;
int limit = Integer.MAX_VALUE;
if (options != null) {
listname = options.getProperty("list");
startsWith = options.getProperty("startsWith");
beginWith = options.getProperty("beginWith");
if (beginWith == null)
beginWith = startsWith;
String lim = options.getProperty("limit");
try {
limit = Integer.parseInt(lim);
} catch (NumberFormatException nfe) {}
}
synchronized(_bf) {
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null)
return Collections.EMPTY_MAP;
SkipIterator iter;
if (startsWith != null)
iter = sl.find(beginWith);
else
iter = sl.iterator();
Map<String, Destination> rv = new HashMap();
for (int i = 0; i < limit && iter.hasNext(); i++) {
String key = (String) iter.nextKey();
if (startsWith != null && !key.startsWith(startsWith))
break;
DestEntry de = (DestEntry) iter.next();
rv.put(key, de.dest);
}
return rv;
} catch (IOException re) {
return Collections.EMPTY_MAP;
} catch (RuntimeException re) {
return Collections.EMPTY_MAP;
}
}
}
/**
* @param options If non-null and contains the key "list", return the
* size of that list (default "hosts.txt", NOT all lists)
*/
@Override
public int size(Properties options) {
String listname = FALLBACK_LIST;
if (options != null) {
String list = options.getProperty("list");
if (list != null) {
listname = list;
}
}
synchronized(_bf) {
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null)
return 0;
return sl.size();
} catch (IOException re) {
return 0;
} catch (RuntimeException re) {
return 0;
}
}
}
////////// End NamingService API
private void dumpDB() {
synchronized(_bf) {
if (_isClosed)
_log.error("Database is closed");
for (String list : _lists) {
try {
SkipList sl = _bf.getIndex(list, _stringSerializer, _destSerializer);
if (sl == null) {
_log.error("No list found for " + list);
continue;
}
int i = 0;
for (SkipIterator iter = sl.iterator(); iter.hasNext(); ) {
String key = (String) iter.nextKey();
DestEntry de = (DestEntry) iter.next();
_log.error("DB " + list + " key " + key + " val " + de);
i++;
}
_log.error(i + " entries found for " + list);
} catch (IOException ioe) {
_log.error("Fail", ioe);
break;
}
}
}
}
public void close() {
synchronized(_bf) {
try {
_bf.close();
} catch (IOException ioe) {
} catch (RuntimeException e) {
}
try {
_raf.close();
} catch (IOException ioe) {
}
_isClosed = true;
}
}
private class Shutdown implements Runnable {
public void run() {
close();
}
}
/**
* UTF-8 Serializer (the one in the lib is US-ASCII).
* Used for all keys.
*/
private static class StringSerializer implements Serializer {
public byte[] getBytes(Object o) {
try {
return ((String) o).getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("No UTF-8", uee);
}
}
public Object construct(byte[] b) {
try {
return new String(b, "UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("No UTF-8", uee);
}
}
}
/**
* Used for the values in the header skiplist
*/
private static class PropertiesSerializer implements Serializer {
public byte[] getBytes(Object o) {
Properties p = (Properties) o;
return DataHelper.toProperties(p);
}
public Object construct(byte[] b) {
Properties rv = new Properties();
try {
DataHelper.fromProperties(b, 0, rv);
} catch (IOException ioe) {
return null;
} catch (DataFormatException dfe) {
return null;
}
return rv;
}
}
/**
* A DestEntry contains Properties and a Destination,
* and is serialized in that order.
*/
private static class DestEntry {
/** may be null */
public Properties props;
/** may not be null */
public Destination dest;
@Override
public String toString() {
return "DestEntry (" + DataHelper.toString(props) +
") " + dest.toString();
}
}
/**
* Used for the values in the addressbook skiplists
*/
private static class DestEntrySerializer implements Serializer {
public byte[] getBytes(Object o) {
DestEntry de = (DestEntry) o;
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
try {
DataHelper.writeProperties(baos, de.props);
de.dest.writeBytes(baos);
} catch (IOException ioe) {
return null;
} catch (DataFormatException dfe) {
return null;
}
return baos.toByteArray();
}
public Object construct(byte[] b) {
DestEntry rv = new DestEntry();
Destination dest = new Destination();
rv.dest = dest;
ByteArrayInputStream bais = new ByteArrayInputStream(b);
try {
rv.props = DataHelper.readProperties(bais);
dest.readBytes(bais);
} catch (IOException ioe) {
return null;
} catch (DataFormatException dfe) {
return null;
}
return rv;
}
}
public static void main(String[] args) {
BlockfileNamingService bns = new BlockfileNamingService(I2PAppContext.getGlobalContext());
//System.out.println("zzz.i2p: " + bns.lookup("zzz.i2p"));
List<String> names = null;
try {
Properties props = new Properties();
DataHelper.loadProps(props, new File("hosts.txt"), true);
names = new ArrayList(props.keySet());
Collections.shuffle(names);
} catch (IOException ioe) {
System.out.println("No hosts.txt to test with");
bns.close();
return;
}
System.out.println("Testing with " + names.size() + " hostnames");
int found = 0;
int notfound = 0;
long start = System.currentTimeMillis();
for (String name : names) {
Destination dest = bns.lookup(name);
if (dest != null)
found++;
else
notfound++;
}
System.out.println("BFNS took " + DataHelper.formatDuration(System.currentTimeMillis() - start));
System.out.println("found " + found + " notfound " + notfound);
bns.dumpDB();
bns.close();
HostsTxtNamingService htns = new HostsTxtNamingService(I2PAppContext.getGlobalContext());
found = 0;
notfound = 0;
start = System.currentTimeMillis();
for (String name : names) {
Destination dest = htns.lookup(name);
if (dest != null)
found++;
else
notfound++;
}
System.out.println("HTNS took " + DataHelper.formatDuration(System.currentTimeMillis() - start));
System.out.println("found " + found + " notfound " + notfound);
}
}

View File

@ -1,52 +0,0 @@
package net.i2p.client.naming;
import java.util.Collection;
import net.i2p.I2PAppContext;
import net.i2p.data.Address;
/**
* @deprecated unused
*/
public class DummyAddressDB extends AddressDB {
public DummyAddressDB(I2PAppContext context) {
super(context);
}
@Override
public Address get(String hostname) {
return null;
}
@Override
public Address put(Address address) {
return null;
}
@Override
public Address remove(String hostname) {
return null;
}
@Override
public Address remove(Address address) {
return null;
}
@Override
public boolean contains(Address address) {
return false;
}
@Override
public boolean contains(String hostname) {
return false;
}
@Override
public Collection hostnames() {
return null;
}
}

View File

@ -7,24 +7,147 @@
*/
package net.i2p.client.naming;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
/**
* A Dummy naming service that can only handle base64 destinations.
* A Dummy naming service that can only handle base64 and b32 destinations.
*/
class DummyNamingService extends NamingService {
private final Map<String, CacheEntry> _cache;
private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32";
protected static final int CACHE_MAX_SIZE = 16;
public static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected DummyNamingService(I2PAppContext context) { super(context); }
private DummyNamingService() { super(null); }
protected DummyNamingService(I2PAppContext context) {
super(context);
_cache = new HashMap(CACHE_MAX_SIZE);
}
@Override
public Destination lookup(String hostname) {
return lookupBase64(hostname);
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
Destination d = getCache(hostname);
if (d != null)
return d;
// If it's long, assume it's a key.
if (hostname.length() >= 516) {
d = lookupBase64(hostname);
// What the heck, cache these too
putCache(hostname, d);
return d;
}
// Try Base32 decoding
if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.endsWith(".b32.i2p") &&
_context.getBooleanPropertyDefaultTrue(PROP_B32)) {
d = LookupDest.lookupBase32Hash(_context, hostname.substring(0, BASE32_HASH_LENGTH));
if (d != null) {
putCache(hostname, d);
return d;
}
}
return null;
}
/**
* Provide basic caching for the service
* The service may override the age and/or size limit
*/
/** Don't know why a dest would ever change but keep this short anyway */
protected static final long CACHE_MAX_AGE = 7*60*1000;
private class CacheEntry {
public Destination dest;
public long exp;
public CacheEntry(Destination d) {
dest = d;
exp = _context.clock().now() + CACHE_MAX_AGE;
}
public boolean isExpired() {
return exp < _context.clock().now();
}
}
/**
* Clean up when full.
* Don't bother removing old entries unless full.
* Caller must synchronize on _cache.
*/
private void cacheClean() {
if (_cache.size() < CACHE_MAX_SIZE)
return;
boolean full = true;
String oldestKey = null;
long oldestExp = Long.MAX_VALUE;
List<String> deleteList = new ArrayList(CACHE_MAX_SIZE);
for (Map.Entry<String, CacheEntry> entry : _cache.entrySet()) {
CacheEntry ce = entry.getValue();
if (ce.isExpired()) {
deleteList.add(entry.getKey());
full = false;
continue;
}
if (oldestKey == null || ce.exp < oldestExp) {
oldestKey = entry.getKey();
oldestExp = ce.exp;
}
}
if (full && oldestKey != null)
deleteList.add(oldestKey);
for (String s : deleteList) {
_cache.remove(s);
}
}
protected void putCache(String s, Destination d) {
if (d == null)
return;
synchronized (_cache) {
_cache.put(s, new CacheEntry(d));
cacheClean();
}
}
protected Destination getCache(String s) {
synchronized (_cache) {
CacheEntry ce = _cache.get(s);
if (ce == null)
return null;
if (ce.isExpired()) {
_cache.remove(s);
return null;
}
return ce.dest;
}
}
/** @since 0.8.5 */
protected void removeCache(String s) {
synchronized (_cache) {
_cache.remove(s);
}
}
/** @since 0.8.1 */
public void clearCache() {
synchronized (_cache) {
_cache.clear();
}
}
}

View File

@ -30,6 +30,7 @@ import net.i2p.data.Destination;
* i2p.naming.eepget.list=http://stats.i2p/cgi-bin/hostquery.cgi?a=,http://i2host.i2p/cgi-bin/i2hostquery?
*
* @author zzz
* @deprecated use HostsTxtNamingService.put()
* @since 0.7.9
*/
public class EepGetAndAddNamingService extends EepGetNamingService {

View File

@ -7,6 +7,7 @@ package net.i2p.client.naming;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
@ -25,6 +26,7 @@ import net.i2p.util.Log;
* Should be used from MetaNamingService, after HostsTxtNamingService.
* Cannot be used as the only NamingService! Be sure any naming service hosts
* are in hosts.txt.
* Supports caching, b32, and b64.
*
* Sample config to put in configadvanced.jsp (restart required):
*
@ -33,7 +35,7 @@ import net.i2p.util.Log;
* i2p.naming.eepget.list=http://namingservice.i2p/cgi-bin/lkup.cgi?host=,http://i2host.i2p/cgi-bin/i2hostquery?
*
*/
public class EepGetNamingService extends NamingService {
public class EepGetNamingService extends DummyNamingService {
private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list";
private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?";
@ -59,22 +61,13 @@ public class EepGetNamingService extends NamingService {
}
@Override
public Destination lookup(String hostname) {
// If it's long, assume it's a key.
if (hostname.length() >= DEST_SIZE)
return lookupBase64(hostname);
hostname = hostname.toLowerCase();
// If you want b32, chain with HostsTxtNamingService
if (hostname.length() == 60 && hostname.endsWith(".b32.i2p"))
return null;
// check the cache
Destination d = getCache(hostname);
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
Destination d = super.lookup(hostname, null, null);
if (d != null)
return d;
hostname = hostname.toLowerCase();
List URLs = getURLs();
if (URLs.isEmpty())
return null;
@ -103,7 +96,6 @@ public class EepGetNamingService extends NamingService {
}
// FIXME allow larger Dests for non-null Certs
private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff
private String fetchAddr(String url, String hostname) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_RESPONSE);

View File

@ -5,6 +5,7 @@
package net.i2p.client.naming;
import java.io.InputStream;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
@ -27,6 +28,7 @@ import net.i2p.util.Log;
*
* Can be used from MetaNamingService, (e.g. after HostsTxtNamingService),
* or as the sole naming service.
* Supports caching, b32, and b64.
*
* Sample chained config to put in configadvanced.jsp (restart required):
*
@ -40,7 +42,7 @@ import net.i2p.util.Log;
* i2p.naming.exec.command=/usr/local/bin/i2presolve
*
*/
public class ExecNamingService extends NamingService {
public class ExecNamingService extends DummyNamingService {
private final static String PROP_EXEC_CMD = "i2p.naming.exec.command";
private final static String DEFAULT_EXEC_CMD = "/usr/local/bin/i2presolve";
@ -59,22 +61,13 @@ public class ExecNamingService extends NamingService {
}
@Override
public Destination lookup(String hostname) {
// If it's long, assume it's a key.
if (hostname.length() >= DEST_SIZE)
return lookupBase64(hostname);
hostname = hostname.toLowerCase();
// If you want b32, chain with HostsTxtNamingService
if (hostname.length() == 60 && hostname.endsWith(".b32.i2p"))
return null;
// check the cache
Destination d = getCache(hostname);
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
Destination d = super.lookup(hostname, null, null);
if (d != null)
return d;
hostname = hostname.toLowerCase();
// lookup
String key = fetchAddr(hostname);
if (key != null) {
@ -87,7 +80,6 @@ public class ExecNamingService extends NamingService {
}
// FIXME allow larger Dests for non-null Certs
private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff
private String fetchAddr(String hostname) {
String[] commandArr = new String[3];

View File

@ -1,132 +0,0 @@
package net.i2p.client.naming;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.Address;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* @deprecated unused
*/
public class FilesystemAddressDB extends AddressDB {
public final static String PROP_ADDRESS_DIR = "i2p.addressdir";
public final static String DEFAULT_ADDRESS_DIR = "addressDb";
private final static Log _log = new Log(FilesystemAddressDB.class);
public FilesystemAddressDB(I2PAppContext context) {
super(context);
//If the address db directory doesn't exist, create it, using the
//contents of hosts.txt.
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File addrDir = new File(dir);
if (!addrDir.exists()) {
addrDir.mkdir();
Properties hosts = new Properties();
File hostsFile = new File("hosts.txt");
if (hostsFile.exists() && hostsFile.canRead()) {
try {
DataHelper.loadProps(hosts, hostsFile);
} catch (IOException ioe) {
_log.error("Error loading hosts file " + hostsFile, ioe);
}
}
Iterator iter = hosts.keySet().iterator();
while (iter.hasNext()) {
String hostname = (String)iter.next();
Address addr = new Address();
addr.setHostname(hostname);
addr.setDestination(hosts.getProperty(hostname));
put(addr);
}
}
}
@Override
public Address get(String hostname) {
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir, hostname);
if (f.exists() && f.canRead()) {
Address addr = new Address();
try {
addr.readBytes(new FileInputStream(f));
} catch (FileNotFoundException exp) {
return null;
} catch (DataFormatException exp) {
_log.error(f.getPath() + " is not a valid address file.");
return null;
} catch (IOException exp) {
_log.error("Error reading " + f.getPath());
return null;
}
return addr;
} else {
_log.warn(f.getPath() + " does not exist.");
return null;
}
}
@Override
public Address put(Address address) {
Address previous = get(address.getHostname());
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir, address.getHostname());
try {
address.writeBytes(new FileOutputStream(f));
} catch (Exception exp) {
_log.error("Error writing " + f.getPath(), exp);
}
return previous;
}
@Override
public Address remove(String hostname) {
Address previous = get(hostname);
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir, hostname);
f.delete();
return previous;
}
@Override
public Address remove(Address address) {
if (contains(address)) {
return remove(address.getHostname());
} else {
return null;
}
}
@Override
public boolean contains(Address address) {
Address inDb = get(address.getHostname());
return inDb.equals(address);
}
@Override
public boolean contains(String hostname) {
return hostnames().contains(hostname);
}
@Override
public Collection hostnames() {
String dir = _context.getProperty(PROP_ADDRESS_DIR, DEFAULT_ADDRESS_DIR);
File f = new File(dir);
return Arrays.asList(f.list());
}
}

View File

@ -7,29 +7,21 @@
*/
package net.i2p.client.naming;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log;
/**
* A naming service based on the "hosts.txt" file.
* A naming service based on multiple "hosts.txt" files.
* Supports .b32.i2p and {b64} lookups.
* Supports caching.
* All host names are converted to lower case.
*/
public class HostsTxtNamingService extends NamingService {
public class HostsTxtNamingService extends MetaNamingService {
/**
* The naming service should only be constructed and accessed through the
@ -37,162 +29,52 @@ public class HostsTxtNamingService extends NamingService {
* appropriate application context itself.
*
*/
public HostsTxtNamingService(I2PAppContext context) { super(context); }
private HostsTxtNamingService() { super(null); }
public HostsTxtNamingService(I2PAppContext context) {
super(context, null);
for (String name : getFilenames()) {
addNamingService(new SingleFileNamingService(context, name), false);
}
}
/**
* If this system property is specified, the tunnel will read the
* given file for hostname=destKey values when resolving names
*/
public final static String PROP_HOSTS_FILE = "i2p.hostsfilelist";
public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32";
/** default hosts.txt filename */
/** default hosts.txt filenames */
public final static String DEFAULT_HOSTS_FILE =
"privatehosts.txt,userhosts.txt,hosts.txt";
private final static Log _log = new Log(HostsTxtNamingService.class);
private List getFilenames() {
private List<String> getFilenames() {
String list = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
StringTokenizer tok = new StringTokenizer(list, ",");
List rv = new ArrayList(tok.countTokens());
List<String> rv = new ArrayList(tok.countTokens());
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
return rv;
}
private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
@Override
public Destination lookup(String hostname) {
Destination d = getCache(hostname);
if (d != null)
return d;
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
// If it's long, assume it's a key.
if (hostname.length() >= 516) {
d = lookupBase64(hostname);
// What the heck, cache these too
putCache(hostname, d);
return d;
}
// Try Base32 decoding
if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.endsWith(".b32.i2p") &&
Boolean.valueOf(_context.getProperty(PROP_B32, "true")).booleanValue()) {
d = LookupDest.lookupBase32Hash(_context, hostname.substring(0, BASE32_HASH_LENGTH));
if (d != null) {
putCache(hostname, d);
return d;
}
}
List filenames = getFilenames();
for (int i = 0; i < filenames.size(); i++) {
String hostsfile = (String)filenames.get(i);
try {
File f = new File(_context.getRouterDir(), hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
String key = getKey(f, hostname.toLowerCase());
if ( (key != null) && (key.trim().length() > 0) ) {
d = lookupBase64(key);
putCache(hostname, d);
return d;
}
} else {
_log.warn("Hosts file " + hostsfile + " does not exist.");
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe);
}
// not found, continue to the next file
}
return null;
if (hostname.length() >= DEST_SIZE)
return lookupBase64(hostname);
return super.lookup(hostname.toLowerCase(), lookupOptions, storedOptions);
}
@Override
public String reverseLookup(Destination dest) {
String destkey = dest.toBase64();
List filenames = getFilenames();
for (int i = 0; i < filenames.size(); i++) {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(_context.getRouterDir(), hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
Set keyset = hosts.keySet();
Iterator iter = keyset.iterator();
while (iter.hasNext()) {
String host = (String)iter.next();
String key = hosts.getProperty(host);
if (destkey.equals(key))
return host;
}
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe);
}
}
return null;
public boolean put(String hostname, Destination d, Properties options) {
return super.put(hostname.toLowerCase(), d, options);
}
/** @deprecated unused */
@Override
public String reverseLookup(Hash h) {
List filenames = getFilenames();
for (int i = 0; i < filenames.size(); i++) {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(_context.getRouterDir(), hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
Set keyset = hosts.keySet();
Iterator iter = keyset.iterator();
while (iter.hasNext()) {
String host = (String)iter.next();
String key = hosts.getProperty(host);
try {
Destination destkey = new Destination();
destkey.fromBase64(key);
if (h.equals(destkey.calculateHash()))
return host;
} catch (DataFormatException dfe) {}
}
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe);
}
}
return null;
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
return super.putIfAbsent(hostname.toLowerCase(), d, options);
}
/**
* Better than DataHelper.loadProps(), doesn't load the whole file into memory,
* and stops when it finds a match.
*
* @param host lower case
* @since 0.7.13
*/
private static String getKey(File file, String host) throws IOException {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), 16*1024);
String line = null;
while ( (line = in.readLine()) != null) {
if (!line.toLowerCase().startsWith(host + '='))
continue;
if (line.indexOf('#') > 0) // trim off any end of line comment
line = line.substring(0, line.indexOf('#')).trim();
int split = line.indexOf('=');
return line.substring(split+1); //.trim() ??????????????
}
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
return null;
@Override
public boolean remove(String hostname, Properties options) {
return super.remove(hostname.toLowerCase(), options);
}
}

View File

@ -2,61 +2,154 @@ package net.i2p.client.naming;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
public class MetaNamingService extends NamingService {
/**
* A naming service of multiple naming services.
* Supports .b32.i2p and {b64} lookups.
* Supports caching.
*/
public class MetaNamingService extends DummyNamingService {
private final static String PROP_NAME_SERVICES = "i2p.nameservicelist";
private final static String DEFAULT_NAME_SERVICES =
"net.i2p.client.naming.PetNameNamingService,net.i2p.client.naming.HostsTxtNamingService";
private List _services;
"net.i2p.client.naming.HostsTxtNamingService";
protected final List<NamingService> _services;
public MetaNamingService(I2PAppContext context) {
super(context);
String list = _context.getProperty(PROP_NAME_SERVICES, DEFAULT_NAME_SERVICES);
StringTokenizer tok = new StringTokenizer(list, ",");
_services = new ArrayList(tok.countTokens());
_services = new CopyOnWriteArrayList();
while (tok.hasMoreTokens()) {
try {
Class cls = Class.forName(tok.nextToken());
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
_services.add(con.newInstance(new Object[] { context }));
addNamingService((NamingService)con.newInstance(new Object[] { context }), false);
} catch (Exception ex) {
_services.add(new DummyNamingService(context)); // fallback
}
}
}
/**
* @param if non-null, services to be added. If null, this will only handle b32 and b64.
* @since 0.8.5
*/
public MetaNamingService(I2PAppContext context, List<NamingService> services) {
super(context);
_services = new CopyOnWriteArrayList();
if (services != null) {
for (NamingService ns : services) {
addNamingService(ns, false);
}
}
}
@Override
public Destination lookup(String hostname) {
Iterator iter = _services.iterator();
while (iter.hasNext()) {
NamingService ns = (NamingService)iter.next();
Destination dest = ns.lookup(hostname);
if (dest != null) {
return dest;
}
}
return lookupBase64(hostname);
public boolean addNamingService(NamingService ns, boolean head) {
if (head)
_services.add(0, ns);
else
_services.add(ns);
return true;
}
@Override
public String reverseLookup(Destination dest) {
Iterator iter = _services.iterator();
while (iter.hasNext()) {
NamingService ns = (NamingService)iter.next();
String hostname = ns.reverseLookup(dest);
if (hostname != null) {
return hostname;
public List<NamingService> getNamingServices() {
return Collections.unmodifiableList(_services);
}
@Override
public boolean removeNamingService(NamingService ns) {
return _services.remove(ns);
}
@Override
public void registerListener(NamingServiceListener nsl) {
for (NamingService ns : _services) {
ns.registerListener(nsl);
}
}
@Override
public void unregisterListener(NamingServiceListener nsl) {
for (NamingService ns : _services) {
ns.unregisterListener(nsl);
}
}
@Override
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
Destination d = super.lookup(hostname, null, null);
if (d != null)
return d;
for (NamingService ns : _services) {
d = ns.lookup(hostname, lookupOptions, storedOptions);
if (d != null) {
putCache(hostname, d);
return d;
}
}
return null;
}
@Override
public String reverseLookup(Destination dest, Properties options) {
for (NamingService ns : _services) {
String host = ns.reverseLookup(dest, options);
if (host != null) {
return host;
}
}
return null;
}
/**
* Stores in the last service
*/
@Override
public boolean put(String hostname, Destination d, Properties options) {
if (_services.isEmpty())
return false;
boolean rv = _services.get(_services.size() - 1).put(hostname, d, options);
// overwrite any previous entry in case it changed
if (rv)
putCache(hostname, d);
return rv;
}
/**
* Stores in the last service
*/
@Override
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
if (_services.isEmpty())
return false;
return _services.get(_services.size() - 1).putIfAbsent(hostname, d, options);
}
/**
* Removes from all services
*/
@Override
public boolean remove(String hostname, Properties options) {
boolean rv = false;
for (NamingService ns : _services) {
if (ns.remove(hostname, options))
rv = true;
}
if (rv)
removeCache(hostname);
return rv;
}
}

View File

@ -9,9 +9,13 @@ package net.i2p.client.naming;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
@ -25,16 +29,13 @@ import net.i2p.util.Log;
public abstract class NamingService {
private final static Log _log = new Log(NamingService.class);
protected I2PAppContext _context;
private /* FIXME final FIXME */ HashMap _cache;
protected final I2PAppContext _context;
protected final Set<NamingServiceListener> _listeners;
/** what classname should be used as the naming service impl? */
public static final String PROP_IMPL = "i2p.naming.impl";
private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
protected static final int CACHE_MAX_SIZE = 16;
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
@ -43,9 +44,7 @@ public abstract class NamingService {
*/
protected NamingService(I2PAppContext context) {
_context = context;
_cache = new HashMap(CACHE_MAX_SIZE);
}
private NamingService() { // nop
_listeners = new CopyOnWriteArraySet();
}
/**
@ -53,7 +52,9 @@ public abstract class NamingService {
* @return the Destination for this host name, or
* <code>null</code> if name is unknown.
*/
public abstract Destination lookup(String hostname);
public Destination lookup(String hostname) {
return lookup(hostname, null, null);
}
/**
* Reverse look up a destination
@ -61,10 +62,12 @@ public abstract class NamingService {
* if none is known. It is safe for subclasses to always return
* <code>null</code> if no reverse lookup is possible.
*/
public String reverseLookup(Destination dest) { return null; };
public String reverseLookup(Destination dest) {
return reverseLookup(dest, null);
}
/** @deprecated unused */
public String reverseLookup(Hash h) { return null; };
public String reverseLookup(Hash h) { return null; }
/**
* Check if host name is valid Base64 encoded dest and return this
@ -82,6 +85,313 @@ public abstract class NamingService {
}
}
///// New API Starts Here
/**
* @return Class simple name by default
* @since 0.8.5
*/
public String getName() {
return getClass().getSimpleName();
}
/**
* @return NamingService-specific options or null
* @since 0.8.5
*/
public Properties getConfiguration() {
return null;
}
/**
* @return success
* @since 0.8.5
*/
public boolean setConfiguration(Properties p) {
return true;
}
// These are for daisy chaining (MetaNamingService)
/**
* @return chained naming services or null
* @since 0.8.5
*/
public List<NamingService> getNamingServices() {
return null;
}
/**
* @return parent naming service or null if this is the root
* @since 0.8.5
*/
public NamingService getParent() {
return null;
}
/**
* Only for chaining-capable NamingServices. Add to end of the list.
* @return success
*/
public boolean addNamingService(NamingService ns) {
return addNamingService(ns, false);
}
/**
* Only for chaining-capable NamingServices
* @param head or tail
* @return success
*/
public boolean addNamingService(NamingService ns, boolean head) {
return false;
}
/**
* Only for chaining-capable NamingServices
* @return success
* @since 0.8.5
*/
public boolean removeNamingService(NamingService ns) {
return false;
}
// options would be used to specify public / private / master ...
// or should we just daisy chain 3 HostsTxtNamingServices ?
// that might be better... then addressbook only talks to the 'router' HostsTxtNamingService
/**
* @return number of entries or -1 if unknown
* @since 0.8.5
*/
public int size() {
return size(null);
}
/**
* @param options NamingService-specific, can be null
* @return number of entries (matching the options if non-null) or -1 if unknown
* @since 0.8.5
*/
public int size(Properties options) {
return -1;
}
/**
* @return all mappings
* or empty Map if none;
* Returned Map is not necessarily sorted, implementation dependent
* @since 0.8.5
*/
public Map<String, Destination> getEntries() {
return getEntries(null);
}
/**
* @param options NamingService-specific, can be null
* @return all mappings (matching the options if non-null)
* or empty Map if none;
* Returned Map is not necessarily sorted, implementation dependent
* @since 0.8.5
*/
public Map<String, Destination> getEntries(Properties options) {
return Collections.EMPTY_MAP;
}
/**
* This may be more or less efficient than getEntries()
* @param options NamingService-specific, can be null
* @return all mappings (matching the options if non-null)
* or empty Map if none;
* Returned Map is not necessarily sorted, implementation dependent
* @since 0.8.5
*/
public Map<String, String> getBase64Entries(Properties options) {
return Collections.EMPTY_MAP;
}
/**
* @return all known host names
* or empty Set if none;
* Returned Set is not necessarily sorted, implementation dependent
* @since 0.8.5
*/
public Set<String> getNames() {
return getNames(null);
}
/**
* @param options NamingService-specific, can be null
* @return all known host names (matching the options if non-null)
* or empty Set if none;
* Returned Set is not necessarily sorted, implementation dependent
* @since 0.8.5
*/
public Set<String> getNames(Properties options) {
return Collections.EMPTY_SET;
}
/**
* @return success
* @since 0.8.5
*/
public boolean put(String hostname, Destination d) {
return put(hostname, d, null);
}
/**
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.5
*/
public boolean put(String hostname, Destination d, Properties options) {
return false;
}
/**
* Fails if entry previously exists
* @return success
* @since 0.8.5
*/
public boolean putIfAbsent(String hostname, Destination d) {
return putIfAbsent(hostname, d, null);
}
/**
* Fails if entry previously exists
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.5
*/
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
return false;
}
/**
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.5
*/
public boolean putAll(Map<String, Destination> entries, Properties options) {
boolean rv = true;
for (Map.Entry<String, Destination> entry : entries.entrySet()) {
if (!put(entry.getKey(), entry.getValue(), options))
rv = false;
}
return rv;
}
/**
* Fails if entry did not previously exist
* @param d may be null if only options are changing
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.5
*/
public boolean update(String hostname, Destination d, Properties options) {
return false;
}
/**
* @return success
* @since 0.8.5
*/
public boolean remove(String hostname) {
return remove(hostname, null);
}
/**
* @param options NamingService-specific, can be null
* @return success
* @since 0.8.5
*/
public boolean remove(String hostname, Properties options) {
return false;
}
/**
* Ask the NamingService to update its database
* Should this be a separate interface? This is what addressbook needs
* @param options NamingService-specific, can be null
* @since 0.8.5
*/
public void requestUpdate(Properties options) {}
/**
* @since 0.8.5
*/
public void registerListener(NamingServiceListener nsl) {
_listeners.add(nsl);
}
/**
* @since 0.8.5
*/
public void unregisterListener(NamingServiceListener nsl) {
_listeners.remove(nsl);
}
/**
* Same as lookup(hostname) but with in and out options
* Note that whether this (and lookup(hostname)) resolve B32 addresses is
* NamingService-specific.
* @param lookupOptions input parameter, NamingService-specific, can be null
* @param storedOptions output parameter, NamingService-specific, any stored properties will be added if non-null
* @return dest or null
* @since 0.8.5
*/
public abstract Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions);
/**
* Same as reverseLookup(dest) but with options
* @param options NamingService-specific, can be null
* @return host name or null
* @since 0.8.5
*/
public String reverseLookup(Destination d, Properties options) {
return null;
}
/**
* Lookup a Base 32 address. This may require the router to fetch the LeaseSet,
* which may take quite a while.
* @param hostname must be {52 chars}.b32.i2p
* @param timeout in seconds; <= 0 means use router default
* @return dest or null
* @since 0.8.5
*/
public Destination lookupBase32(String hostname, int timeout) {
return null;
}
/**
* Same as lookupB32 but with the SHA256 Hash precalculated
* @param timeout in seconds; <= 0 means use router default
* @return dest or null
* @since 0.8.5
*/
public Destination lookup(Hash hash, int timeout) {
return null;
}
/**
* Parent will call when added.
* If this is the root naming service, the core will start it.
* Should not be called by others.
* @since 0.8.5
*/
public void start() {}
/**
* Parent will call when removed.
* If this is the root naming service, the core will stop it.
* Should not be called by others.
* @since 0.8.5
*/
public void stop() {}
//// End New API
/**
* Get a naming service instance. This method ensures that there
* will be only one naming service instance (singleton) as well as
@ -96,89 +406,10 @@ public abstract class NamingService {
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
instance = (NamingService)con.newInstance(new Object[] { context });
} catch (Exception ex) {
_log.error("Cannot loadNaming service implementation", ex);
_log.error("Cannot load naming service " + impl, ex);
instance = new DummyNamingService(context); // fallback
}
return instance;
}
/**
* Provide basic caching for the service
* The service may override the age and/or size limit
*/
/** Don't know why a dest would ever change but keep this short anyway */
protected static final long CACHE_MAX_AGE = 7*60*1000;
private class CacheEntry {
public Destination dest;
public long exp;
public CacheEntry(Destination d) {
dest = d;
exp = _context.clock().now() + CACHE_MAX_AGE;
}
public boolean isExpired() {
return exp < _context.clock().now();
}
}
/**
* Clean up when full.
* Don't bother removing old entries unless full.
* Caller must synchronize on _cache.
*/
private void cacheClean() {
if (_cache.size() < CACHE_MAX_SIZE)
return;
boolean full = true;
Object oldestKey = null;
long oldestExp = Long.MAX_VALUE;
ArrayList deleteList = new ArrayList(CACHE_MAX_SIZE);
for (Iterator iter = _cache.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
CacheEntry ce = (CacheEntry) entry.getValue();
if (ce.isExpired()) {
deleteList.add(entry.getKey());
full = false;
continue;
}
if (oldestKey == null || ce.exp < oldestExp) {
oldestKey = entry.getKey();
oldestExp = ce.exp;
}
}
if (full && oldestKey != null)
deleteList.add(oldestKey);
for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) {
_cache.remove(iter.next());
}
}
protected void putCache(String s, Destination d) {
if (d == null)
return;
synchronized (_cache) {
_cache.put(s, new CacheEntry(d));
cacheClean();
}
}
protected Destination getCache(String s) {
synchronized (_cache) {
CacheEntry ce = (CacheEntry) _cache.get(s);
if (ce == null)
return null;
if (ce.isExpired()) {
_cache.remove(s);
return null;
}
return ce.dest;
}
}
/** @since 0.8.1 */
public void clearCache() {
synchronized (_cache) {
_cache.clear();
}
}
}

View File

@ -0,0 +1,25 @@
package net.i2p.client.naming;
import java.util.Properties;
import net.i2p.data.Destination;
public interface NamingServiceListener {
/** also called when a NamingService is added or removed */
public void configurationChanged(NamingService ns);
/**
* @param options NamingService-specific, can be null
*/
public void entryAdded(NamingService ns, String hostname, Destination dest, Properties options);
/**
* @param dest null if unchanged
* @param options NamingService-specific, can be null
*/
public void entryChanged(NamingService ns, String hostname, Destination dest, Properties options);
public void entryRemoved(NamingService ns, String hostname);
}

View File

@ -1,178 +0,0 @@
package net.i2p.client.naming;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.data.DataHelper;
/**
* deprecated unused but can be instantiated through I2PAppContext
*/
public class PetName {
private String _name;
private String _network;
private String _protocol;
private List _groups;
private boolean _isPublic;
private String _location;
public PetName() {
this(null, null, null, null);
}
public PetName(String name, String network, String protocol, String location) {
_name = name;
_network = network;
_protocol = protocol;
_location = location;
_groups = new ArrayList();
_isPublic = false;
}
/**
* @param dbLine name:network:protocol:isPublic:group1,group2,group3:location
*/
public PetName(String dbLine) {
_groups = new ArrayList();
StringTokenizer tok = new StringTokenizer(dbLine, ":\n", true);
int tokens = tok.countTokens();
//System.out.println("Tokens: " + tokens);
if (tokens < 7) {
return;
}
String s = tok.nextToken();
if (":".equals(s)) {
_name = null;
} else {
_name = s;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
_network = null;
} else {
_network = s;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
_protocol = null;
} else {
_protocol = s;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
_isPublic = false;
} else {
if ("true".equals(s))
_isPublic = true;
else
_isPublic = false;
s = tok.nextToken(); // skip past the :
}
s = tok.nextToken();
if (":".equals(s)) {
// noop
} else {
StringTokenizer gtok = new StringTokenizer(s, ",");
while (gtok.hasMoreTokens())
_groups.add(gtok.nextToken().trim());
s = tok.nextToken(); // skip past the :
}
while (tok.hasMoreTokens()) {
if (_location == null)
_location = tok.nextToken();
else
_location = _location + tok.nextToken();
}
}
public String getName() { return _name; }
public String getNetwork() { return _network; }
public String getProtocol() { return _protocol; }
public String getLocation() { return _location; }
public boolean getIsPublic() { return _isPublic; }
public int getGroupCount() { return _groups.size(); }
public String getGroup(int i) { return (String)_groups.get(i); }
public void setName(String name) { _name = name; }
public void setNetwork(String network) { _network = network; }
public void setProtocol(String protocol) { _protocol = protocol; }
public void setLocation(String location) { _location = location; }
public void setIsPublic(boolean pub) { _isPublic = pub; }
public void addGroup(String name) {
if ( (name != null) && (name.length() > 0) && (!_groups.contains(name)) )
_groups.add(name);
}
public void removeGroup(String name) { _groups.remove(name); }
public void setGroups(String groups) {
if (groups != null) {
_groups.clear();
StringTokenizer tok = new StringTokenizer(groups, ", \t");
while (tok.hasMoreTokens())
addGroup(tok.nextToken().trim());
} else {
_groups.clear();
}
}
public boolean isMember(String group) {
for (int i = 0; i < getGroupCount(); i++)
if (getGroup(i).equals(group))
return true;
return false;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(256);
if (_name != null) buf.append(_name.trim());
buf.append(':');
if (_network != null) buf.append(_network.trim());
buf.append(':');
if (_protocol != null) buf.append(_protocol.trim());
buf.append(':').append(_isPublic).append(':');
if (_groups != null) {
for (int i = 0; i < _groups.size(); i++) {
buf.append(((String)_groups.get(i)).trim());
if (i + 1 < _groups.size())
buf.append(',');
}
}
buf.append(':');
if (_location != null) buf.append(_location.trim());
return buf.toString();
}
@Override
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof PetName) ) return false;
PetName pn = (PetName)obj;
return DataHelper.eq(_name, pn._name) &&
DataHelper.eq(_location, pn._location) &&
DataHelper.eq(_network, pn._network) &&
DataHelper.eq(_protocol, pn._protocol);
}
@Override
public int hashCode() {
int rv = 0;
rv += DataHelper.hashCode(_name);
rv += DataHelper.hashCode(_location);
rv += DataHelper.hashCode(_network);
rv += DataHelper.hashCode(_protocol);
return rv;
}
public static void main(String args[]) {
test("a:b:c:true:e:f");
test("a:::true::d");
test("a:::true::");
test("a:b::true::");
test(":::trye::");
test("a:b:c:true:e:http://foo.bar");
}
private static void test(String line) {
PetName pn = new PetName(line);
String val = pn.toString();
System.out.println("OK? " + val.equals(line) + ": " + line + " [" + val + "]");
}
}

View File

@ -1,117 +0,0 @@
package net.i2p.client.naming;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* deprecated unused but can be instantiated through I2PAppContext
*/
public class PetNameDB {
/** name (String) to PetName mapping */
private final Map _names;
private String _path;
public PetNameDB() {
_names = Collections.synchronizedMap(new HashMap());
}
public PetName getByName(String name) {
if ( (name == null) || (name.length() <= 0) ) return null;
return (PetName)_names.get(name.toLowerCase());
}
public void add(PetName pn) {
if ( (pn == null) || (pn.getName() == null) ) return;
_names.put(pn.getName().toLowerCase(), pn);
}
public void clear() { _names.clear(); }
public boolean contains(PetName pn) { return _names.containsValue(pn); }
public boolean containsName(String name) {
if ( (name == null) || (name.length() <= 0) ) return false;
return _names.containsKey(name.toLowerCase());
}
public boolean isEmpty() { return _names.isEmpty(); }
public Iterator iterator() { return new ArrayList(_names.values()).iterator(); }
public void remove(PetName pn) {
if (pn != null) _names.remove(pn.getName().toLowerCase());
}
public void removeName(String name) {
if (name != null) _names.remove(name.toLowerCase());
}
public int size() { return _names.size(); }
public Set getNames() { return new HashSet(_names.keySet()); }
public List getGroups() {
List rv = new ArrayList();
for (Iterator iter = iterator(); iter.hasNext(); ) {
PetName name = (PetName)iter.next();
for (int i = 0; i < name.getGroupCount(); i++)
if (!rv.contains(name.getGroup(i)))
rv.add(name.getGroup(i));
}
return rv;
}
public PetName getByLocation(String location) {
if (location == null) return null;
synchronized (_names) {
for (Iterator iter = iterator(); iter.hasNext(); ) {
PetName name = (PetName)iter.next();
if ( (name.getLocation() != null) && (name.getLocation().trim().equals(location.trim())) )
return name;
}
}
return null;
}
public void load(String location) throws IOException {
_path = location;
File f = new File(location);
if (!f.exists()) return;
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
String line = null;
while ( (line = in.readLine()) != null) {
PetName name = new PetName(line);
if (name.getName() != null)
add(name);
}
} finally {
in.close();
}
}
public void store(String location) throws IOException {
Writer out = null;
try {
out = new OutputStreamWriter(new FileOutputStream(location), "UTF-8");
for (Iterator iter = iterator(); iter.hasNext(); ) {
PetName name = (PetName)iter.next();
if (name != null)
out.write(name.toString() + "\n");
}
} finally {
out.close();
}
}
public void store() throws IOException {
if (_path != null) {
store(_path);
}
}
}

View File

@ -1,66 +0,0 @@
package net.i2p.client.naming;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
/**
* @deprecated unused
*/
public class PetNameNamingService extends NamingService {
private PetNameDB _petnameDb;
public final static String PROP_PETNAME_FILE = "i2p.petnamefile";
public final static String DEFAULT_PETNAME_FILE = "petnames.txt";
public PetNameNamingService(I2PAppContext context) {
super(context);
_petnameDb = _context.petnameDb();
String file = _context.getProperty(PROP_PETNAME_FILE, DEFAULT_PETNAME_FILE);
//If the petnamedb file doesn't exist, create it, using the
//contents of hosts.txt.
// File nameFile = new File(file);
// if (!nameFile.exists()) {
// Properties hosts = new Properties();
// File hostsFile = new File("hosts.txt");
// if (hostsFile.exists() && hostsFile.canRead()) {
// try {
// DataHelper.loadProps(hosts, hostsFile);
// } catch (IOException ioe) {
// }
// }
// Iterator iter = hosts.keySet().iterator();
// while (iter.hasNext()) {
// String hostname = (String)iter.next();
// PetName pn = new PetName(hostname, "i2p", "http", hosts.getProperty(hostname));
// _petnameDb.set(hostname, pn);
// }
// try {
// _petnameDb.store(file);
// } catch (IOException ioe) {
// }
// }
try {
_petnameDb.load(file);
} catch (IOException ioe) {
}
}
@Override
public Destination lookup(String hostname) {
PetName name = _petnameDb.getByName(hostname);
if (name != null && name.getNetwork().equalsIgnoreCase("i2p")) {
return lookupBase64(name.getLocation());
} else {
return lookupBase64(hostname);
}
}
@Override
public String reverseLookup(Destination dest) {
return _petnameDb.getByLocation(dest.toBase64()).getName();
}
}

View File

@ -0,0 +1,346 @@
/*
* free (adj.): unencumbered; not under the control of others
* Written by mihi in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*/
package net.i2p.client.naming;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
/**
* A naming service based on a single file using the "hosts.txt" format.
* Supports adds, removes, and listeners.
*
* All methods here are case-sensitive.
* Conversion to lower case is done in HostsTxtNamingService.
*
* This does NOT provide .b32.i2p or {b64} resolution.
* It also does not do any caching.
* Use from HostsTxtNamingService or chain with another NamingService
* via MetaNamingService if you need those features.
*
* @since 0.8.5
*/
public class SingleFileNamingService extends NamingService {
private final static Log _log = new Log(SingleFileNamingService.class);
private final File _file;
private final ReentrantReadWriteLock _fileLock;
public SingleFileNamingService(I2PAppContext context, String filename) {
super(context);
File file = new File(filename);
if (!file.isAbsolute())
file = new File(context.getRouterDir(), filename);
_file = file;
_fileLock = new ReentrantReadWriteLock(true);
}
/**
* @return the base file name
*/
@Override
public String getName() {
return _file.getAbsolutePath();
}
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param lookupOptions ignored
* @param storedOptions ignored
*/
@Override
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
try {
String key = getKey(hostname);
if (key != null)
return lookupBase64(key);
} catch (Exception ioe) {
if (_file.exists())
_log.error("Error loading hosts file " + _file, ioe);
else if (_log.shouldLog(Log.WARN))
_log.warn("Error loading hosts file " + _file, ioe);
}
return null;
}
/**
* @param options ignored
*/
@Override
public String reverseLookup(Destination dest, Properties options) {
String destkey = dest.toBase64();
Properties hosts = new Properties();
getReadLock();
try {
DataHelper.loadProps(hosts, _file, true);
} catch (Exception ioe) {
if (_file.exists())
_log.error("Error loading hosts file " + _file, ioe);
else if (_log.shouldLog(Log.WARN))
_log.warn("Error loading hosts file " + _file, ioe);
return null;
} finally {
releaseReadLock();
}
Set keyset = hosts.keySet();
Iterator iter = keyset.iterator();
while (iter.hasNext()) {
String host = (String)iter.next();
String key = hosts.getProperty(host);
if (destkey.equals(key))
return host;
}
return null;
}
/**
* Better than DataHelper.loadProps(), doesn't load the whole file into memory,
* and stops when it finds a match.
*
* @param host case-sensitive; caller should convert to lower case
*/
private String getKey(String host) throws IOException {
BufferedReader in = null;
getReadLock();
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
String line = null;
String search = host + '=';
while ( (line = in.readLine()) != null) {
if (!line.startsWith(search))
continue;
if (line.indexOf('#') > 0) // trim off any end of line comment
line = line.substring(0, line.indexOf('#')).trim();
int split = line.indexOf('=');
return line.substring(split+1); //.trim() ??????????????
}
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
releaseReadLock();
}
return null;
}
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param options ignored
*/
@Override
public boolean put(String hostname, Destination d, Properties options) {
// try easy way first, most adds are not replaces
if (putIfAbsent(hostname, d, options))
return true;
if (!getWriteLock())
return false;
BufferedReader in = null;
BufferedWriter out = null;
try {
File tmp = SecureFile.createTempFile("temp-", ".tmp", _file.getAbsoluteFile().getParentFile());
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8"));
if (_file.exists()) {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
String line = null;
String search = hostname + '=';
while ( (line = in.readLine()) != null) {
if (line.startsWith(search))
continue;
out.write(line);
out.newLine();
}
in.close();
}
out.write(hostname);
out.write('=');
out.write(d.toBase64());
out.newLine();
out.close();
boolean success = rename(tmp, _file);
if (success) {
for (NamingServiceListener nsl : _listeners) {
nsl.entryChanged(this, hostname, d, options);
}
}
return success;
} catch (IOException ioe) {
if (in != null) try { in.close(); } catch (IOException e) {}
if (out != null) try { out.close(); } catch (IOException e) {}
_log.error("Error adding " + hostname, ioe);
return false;
} finally { releaseWriteLock(); }
}
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param options ignored
*/
@Override
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
if (!getWriteLock())
return false;
OutputStream out = null;
try {
// simply check if present, and if not, append
try {
if (getKey(hostname) != null)
return false;
} catch (IOException ioe) {
if (_file.exists()) {
_log.error("Error adding " + hostname, ioe);
return false;
}
// else new file
}
out = new SecureFileOutputStream(_file, true);
// FIXME fails if previous last line didn't have a trailing \n
out.write(hostname.getBytes("UTF-8"));
out.write('=');
out.write(d.toBase64().getBytes());
out.write('\n');
out.close();
for (NamingServiceListener nsl : _listeners) {
nsl.entryAdded(this, hostname, d, options);
}
return true;
} catch (IOException ioe) {
if (out != null) try { out.close(); } catch (IOException e) {}
_log.error("Error adding " + hostname, ioe);
return false;
} finally { releaseWriteLock(); }
}
/**
* @param hostname case-sensitive; caller should convert to lower case
* @param options ignored
*/
@Override
public boolean remove(String hostname, Properties options) {
if (!getWriteLock())
return false;
if (!_file.exists())
return false;
BufferedReader in = null;
BufferedWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
File tmp = SecureFile.createTempFile("temp-", ".tmp", _file.getAbsoluteFile().getParentFile());
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8"));
String line = null;
String search = hostname + '=';
boolean success = false;
while ( (line = in.readLine()) != null) {
if (line.startsWith(search)) {
success = true;
continue;
}
out.write(line);
out.newLine();
}
in.close();
out.close();
if (!success) {
tmp.delete();
return false;
}
success = rename(tmp, _file);
if (success) {
for (NamingServiceListener nsl : _listeners) {
nsl.entryRemoved(this, hostname);
}
}
return success;
} catch (IOException ioe) {
if (in != null) try { in.close(); } catch (IOException e) {}
if (out != null) try { out.close(); } catch (IOException e) {}
_log.error("Error removing " + hostname, ioe);
return false;
} finally {
releaseWriteLock();
}
}
private static boolean rename(File from, File to) {
boolean success = false;
boolean isWindows = System.getProperty("os.name").startsWith("Win");
// overwrite fails on windows
if (!isWindows)
success = from.renameTo(to);
if (!success) {
to.delete();
success = from.renameTo(to);
if (!success) {
// hard way
success = FileUtil.copy(from.getAbsolutePath(), to.getAbsolutePath(), true, true);
from.delete();
}
}
return success;
}
private void getReadLock() {
_fileLock.readLock().lock();
}
private void releaseReadLock() {
_fileLock.readLock().unlock();
}
/** @return true if the lock was acquired */
private boolean getWriteLock() {
try {
boolean rv = _fileLock.writeLock().tryLock(10000, TimeUnit.MILLISECONDS);
if ((!rv) && _log.shouldLog(Log.WARN))
_log.warn("no lock, size is: " + _fileLock.getQueueLength(), new Exception("rats"));
return rv;
} catch (InterruptedException ie) {}
return false;
}
private void releaseWriteLock() {
_fileLock.writeLock().unlock();
}
public static void main(String[] args) {
NamingService ns = new SingleFileNamingService(I2PAppContext.getGlobalContext(), "hosts.txt");
Destination d = new Destination();
try {
d.readBytes(new byte[387], 0);
} catch (DataFormatException dfe) {}
boolean b = ns.put("aaaaa", d);
System.out.println("Test 1 pass? " + b);
b = ns.put("bbbbb", d);
System.out.println("Test 2 pass? " + b);
b = ns.remove("aaaaa");
System.out.println("Test 3 pass? " + b);
b = ns.lookup("aaaaa") == null;
System.out.println("Test 4 pass? " + b);
b = ns.lookup("bbbbb") != null;
System.out.println("Test 5 pass? " + b);
b = !ns.putIfAbsent("bbbbb", d);
System.out.println("Test 6 pass? " + b);
}
}

View File

@ -0,0 +1,33 @@
Version 0.1.1 from http://www.metanotion.net/software/sandbox/block.html
License: See any source file.
Changes for i2p:
- BSkipList has an option to not keep everything in memory.
When this option is enabled, we use the new IBSkipSpan instead of
BSkipSpan. IBSkipSpan has the following changes:
* Only the first key in the span, and no values, are stored in memory
* put() and remove() read the span keys and values in from disk first
* flush() nulls out the keys and values after flushing to disk
* get() does a linear search through the keys on disk
- The metaIndex is stored in-memory. All "user" skiplists are not
stored in-memory.
- Default span size changed from 127 to 16
- Use I2P random source
- Return the previous SkipList if still open from a call to getIndex()
- Add a closeIndex() method
- Commented out some System.out.println()
- Convert Errors without message or cause to RuntimeExceptions with a message and cause
TODO:
- Change PAGESIZE from default 1024 to 4096? No, wastes too much disk.

View File

@ -0,0 +1,136 @@
/*
Copyright (c) 2006, Matthew Estes
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Metanotion Software nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.metanotion.io;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RAIFile implements RandomAccessInterface, DataInput, DataOutput {
private File f;
private RandomAccessFile delegate;
private boolean r=false, w=false;
public RAIFile(RandomAccessFile file) throws FileNotFoundException {
this.f = null;
this.delegate = file;
}
public RAIFile(File file, boolean read, boolean write) throws FileNotFoundException {
this.f = file;
this.r = read;
this.w = write;
String mode = "";
if(this.r) { mode += "r"; }
if(this.w) { mode += "w"; }
this.delegate = new RandomAccessFile(file, mode);
}
public long getFilePointer() throws IOException { return delegate.getFilePointer(); }
public long length() throws IOException { return delegate.length(); }
public int read() throws IOException { return delegate.read(); }
public int read(byte[] b) throws IOException { return delegate.read(b); }
public int read(byte[] b, int off, int len) throws IOException { return delegate.read(b,off,len); }
public void seek(long pos) throws IOException { delegate.seek(pos); }
public void setLength(long newLength) throws IOException { delegate.setLength(newLength); }
// Closeable Methods
// TODO May need to change.
public void close() throws IOException { delegate.close(); }
// DataInput Methods
public boolean readBoolean() throws IOException { return delegate.readBoolean(); }
public byte readByte() throws IOException { return delegate.readByte(); }
public char readChar() throws IOException { return delegate.readChar(); }
public double readDouble() throws IOException { return delegate.readDouble(); }
public float readFloat() throws IOException { return delegate.readFloat(); }
public void readFully(byte[] b) throws IOException { delegate.readFully(b); }
public void readFully(byte[] b, int off, int len) throws IOException { delegate.readFully(b,off,len); }
public int readInt() throws IOException { return delegate.readInt(); }
public String readLine() throws IOException { return delegate.readLine(); }
public long readLong() throws IOException { return delegate.readLong(); }
public short readShort() throws IOException { return delegate.readShort(); }
public int readUnsignedByte() throws IOException { return delegate.readUnsignedByte(); }
public int readUnsignedShort() throws IOException { return delegate.readUnsignedShort(); }
/** Read a UTF encoded string
I would delegate here. But Java's read/writeUTF combo suck.
A signed 2 byte length is not enough.
This reads a 4 byte length.
The upper byte MUST be zero, if its not, then its not this method and has used an
extensible length encoding.
This is followed by the bytes of the UTF encoded string, as
returned by String.getBytes("UTF-8");
*/
public String readUTF() throws IOException {
int len = delegate.readInt();
if((len < 0) || (len >= 16777216)) { throw new IOException("Bad Length Encoding"); }
byte[] bytes = new byte[len];
int l = delegate.read(bytes);
if(l==-1) { throw new IOException("EOF while reading String"); }
String s = new String(bytes, "UTF-8");
return s;
}
public int skipBytes(int n) throws IOException { return delegate.skipBytes(n); }
// DataOutput Methods
public void write(int b) throws IOException { delegate.write(b); }
public void write(byte[] b) throws IOException { delegate.write(b); }
public void write(byte[] b, int off, int len) throws IOException { delegate.write(b,off,len); }
public void writeBoolean(boolean v) throws IOException { delegate.writeBoolean(v); }
public void writeByte(int v) throws IOException { delegate.writeByte(v); }
public void writeShort(int v) throws IOException { delegate.writeShort(v); }
public void writeChar(int v) throws IOException { delegate.writeChar(v); }
public void writeInt(int v) throws IOException { delegate.writeInt(v); }
public void writeLong(long v) throws IOException { delegate.writeLong(v); }
public void writeFloat(float v) throws IOException { delegate.writeFloat(v); }
public void writeDouble(double v) throws IOException { delegate.writeDouble(v); }
public void writeBytes(String s) throws IOException { delegate.writeBytes(s); }
public void writeChars(String s) throws IOException { delegate.writeChars(s); }
/** Write a UTF encoded string
I would delegate here. But Java's read/writeUTF combo suck.
A signed 2 byte length is not enough.
This writes a 4 byte length.
The upper byte MUST be zero, if its not, then its not this method and has used an
extensible length encoding.
This is followed by the bytes of the UTF encoded string, as
returned by String.getBytes("UTF-8");
*/
public void writeUTF(String str) throws IOException {
byte[] string = str.getBytes("UTF-8");
if(string.length >= 16777216) { throw new IOException("String to long for encoding type"); }
delegate.writeInt(string.length);
delegate.write(string);
}
}

View File

@ -0,0 +1,77 @@
/*
Copyright (c) 2006, Matthew Estes
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Metanotion Software nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.metanotion.io;
import java.io.IOException;
public interface RandomAccessInterface {
public long getFilePointer() throws IOException;
public long length() throws IOException;
public int read() throws IOException;
public int read(byte[] b) throws IOException;
public int read(byte[] b, int off, int len) throws IOException;
public void seek(long pos) throws IOException;
public void setLength(long newLength) throws IOException;
// Closeable Methods
public void close() throws IOException;
// DataInput Methods
public boolean readBoolean() throws IOException;
public byte readByte() throws IOException;
public char readChar() throws IOException;
public double readDouble() throws IOException;
public float readFloat() throws IOException;
public void readFully(byte[] b) throws IOException;
public void readFully(byte[] b, int off, int len) throws IOException;
public int readInt() throws IOException;
public String readLine() throws IOException;
public long readLong() throws IOException;
public short readShort() throws IOException;
public int readUnsignedByte() throws IOException;
public int readUnsignedShort() throws IOException;
public String readUTF() throws IOException;
public int skipBytes(int n) throws IOException;
// DataOutput Methods
public void write(int b) throws IOException;
public void write(byte[] b) throws IOException;
public void write(byte[] b, int off, int len) throws IOException;
public void writeBoolean(boolean v) throws IOException;
public void writeByte(int v) throws IOException;
public void writeShort(int v) throws IOException;
public void writeChar(int v) throws IOException;
public void writeInt(int v) throws IOException;
public void writeLong(long v) throws IOException;
public void writeFloat(float v) throws IOException;
public void writeDouble(double v) throws IOException;
public void writeBytes(String s) throws IOException;
public void writeChars(String s) throws IOException;
public void writeUTF(String str) throws IOException;
}

View File

@ -0,0 +1,62 @@
/*
Copyright (c) 2006, Matthew Estes
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Metanotion Software nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.metanotion.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import net.metanotion.io.Serializer;
public abstract class SerialStreams implements Serializer {
public byte[] getBytes(Object o) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
writeOut(dos, o);
return baos.toByteArray();
} catch (IOException ioe) { throw new Error(); }
}
public Object construct(byte[] b) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(b);
DataInputStream dis = new DataInputStream(bais);
return readIn(dis);
} catch (IOException ioe) {
ioe.printStackTrace();
throw new Error();
}
}
abstract public void writeOut(DataOutputStream dos, Object o) throws IOException;
abstract public Object readIn(DataInputStream dis) throws IOException;
}

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2006, Matthew Estes
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Metanotion Software nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.metanotion.io;
public interface Serializer {
public byte[] getBytes(Object o);
public Object construct(byte[] b);
}

View File

@ -0,0 +1,314 @@
/*
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.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import net.metanotion.io.RAIFile;
import net.metanotion.io.RandomAccessInterface;
import net.metanotion.io.Serializer;
import net.metanotion.io.data.IntBytes;
import net.metanotion.io.data.LongBytes;
import net.metanotion.io.data.NullBytes;
import net.metanotion.io.data.StringBytes;
import net.metanotion.io.block.index.BSkipList;
import net.metanotion.util.skiplist.SkipList;
class CorruptFileException extends IOException { }
class BadFileFormatException extends IOException { }
class BadVersionException extends IOException { }
public class BlockFile {
public static final long PAGESIZE = 1024;
public static final long OFFSET_MOUNTED = 20;
public RandomAccessInterface file;
private long magicBytes = 0x3141deadbeef0100L;
private long fileLen = PAGESIZE * 2;
private int freeListStart = 0;
private short mounted = 0;
public short spanSize = 16;
private BSkipList metaIndex = null;
private HashMap openIndices = new HashMap();
private void mount() throws IOException {
file.seek(BlockFile.OFFSET_MOUNTED);
mounted = 1;
file.writeShort(mounted);
}
private void writeSuperBlock() throws IOException {
file.seek(0);
file.writeLong( magicBytes);
file.writeLong( fileLen);
file.writeInt( freeListStart);
file.writeShort(mounted);
file.writeShort(spanSize);
}
private void readSuperBlock() throws IOException {
file.seek(0);
magicBytes = file.readLong();
fileLen = file.readLong();
freeListStart = file.readInt();
mounted = file.readShort();
spanSize = file.readShort();
}
public static void main(String args[]) {
try {
RAIFile raif = new RAIFile(new File(args[0]), true, true);
BlockFile bf = new BlockFile(raif, true);
//bf.metaIndex.delete();
bf.makeIndex("foo", new NullBytes(), new NullBytes());
BSkipList b = bf.getIndex("foo", new NullBytes(), new NullBytes());
System.out.println(bf.allocPage());
bf.close();
raif.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public int writeMultiPageData(byte[] data, int page, int[] curPageOff, int[] nextPage) throws IOException {
int pageCounter = curPageOff[0];
int curNextPage = nextPage[0];
int curPage = page;
int dct = 0;
while(dct < data.length) {
int len = ((int) BlockFile.PAGESIZE) - pageCounter;
if(len <= 0) {
if(curNextPage==0) {
curNextPage = this.allocPage();
BlockFile.pageSeek(this.file, curNextPage);
this.file.writeInt(0);
BlockFile.pageSeek(this.file, curPage);
this.file.writeInt(curNextPage);
}
BlockFile.pageSeek(this.file, curNextPage);
curPage = curNextPage;
curNextPage = this.file.readInt();
pageCounter = 4;
len = ((int) BlockFile.PAGESIZE) - pageCounter;
}
this.file.write(data, dct, Math.min(len, data.length - dct));
pageCounter += Math.min(len, data.length - dct);
dct += Math.min(len, data.length - dct);
}
nextPage[0] = curNextPage;
curPageOff[0] = pageCounter;
return curPage;
}
public int readMultiPageData(byte[] arr, int page, int[] curPageOff, int[] nextPage) throws IOException {
int pageCounter = curPageOff[0];
int curNextPage = nextPage[0];
int curPage = page;
int dct = 0;
int res;
while(dct < arr.length) {
int len = ((int) BlockFile.PAGESIZE) - pageCounter;
if(len <= 0) {
BlockFile.pageSeek(this.file, curNextPage);
curPage = curNextPage;
curNextPage = this.file.readInt();
pageCounter = 4;
len = ((int) BlockFile.PAGESIZE) - pageCounter;
}
res = this.file.read(arr, dct, Math.min(len, arr.length - dct));
if(res == -1) { throw new IOException(); }
pageCounter += Math.min(len, arr.length - dct);
dct += res;
}
nextPage[0] = curNextPage;
curPageOff[0] = pageCounter;
return curPage;
}
public BlockFile(RandomAccessInterface rai) throws IOException { this(rai, false); }
public BlockFile(RandomAccessFile raf) throws IOException { this(new RAIFile(raf), false); }
public BlockFile(RandomAccessFile raf, boolean init) throws IOException { this(new RAIFile(raf), init); }
public BlockFile(File f, boolean init) throws IOException { this(new RAIFile(f, true, true), init); }
public BlockFile(RandomAccessInterface rai, boolean init) throws IOException {
if(rai==null) { throw new NullPointerException(); }
file = rai;
if(init) {
file.setLength(fileLen);
writeSuperBlock();
BSkipList.init(this, 2, spanSize);
}
readSuperBlock();
if(magicBytes != 0x3141deadbeef0100L) {
if((magicBytes & 0x3141deadbeef0000L) == 0x3141deadbeef0000L) {
throw new BadVersionException();
} else {
throw new BadFileFormatException();
}
}
// if(mounted != 0) { throw new CorruptFileException(); }
if(fileLen != file.length()) { throw new CorruptFileException(); }
mount();
metaIndex = new BSkipList(spanSize, this, 2, new StringBytes(), new IntBytes());
}
public static void pageSeek(RandomAccessInterface file, int page) throws IOException { file.seek((((long)page) - 1L) * BlockFile.PAGESIZE ); }
public int allocPage() throws IOException {
if(freeListStart != 0) {
FreeListBlock flb = new FreeListBlock(file, freeListStart);
if(flb.len > 0) {
flb.len = flb.len - 1;
int page = flb.branches[flb.len];
flb.writeBlock();
return page;
} else {
freeListStart = flb.nextPage;
writeSuperBlock();
return flb.page;
}
}
long offset = file.length();
fileLen = offset + BlockFile.PAGESIZE;
file.setLength(fileLen);
writeSuperBlock();
return ((int) ((long) (offset / BlockFile.PAGESIZE))) + 1;
}
public void freePage(int page) throws IOException {
System.out.println("Free Page " + page);
if(freeListStart == 0) {
freeListStart = page;
FreeListBlock.initPage(file, page);
writeSuperBlock();
return;
}
FreeListBlock flb = new FreeListBlock(file, freeListStart);
if(flb.isFull()) {
FreeListBlock.initPage(file, page);
if(flb.nextPage == 0) {
flb.nextPage = page;
flb.writeBlock();
return;
} else {
flb = new FreeListBlock(file, page);
flb.nextPage = freeListStart;
flb.writeBlock();
freeListStart = page;
writeSuperBlock();
return;
}
}
flb.addPage(page);
flb.writeBlock();
}
public BSkipList getIndex(String name, Serializer key, Serializer val) throws IOException {
// added I2P
BSkipList bsl = (BSkipList) openIndices.get(name);
if (bsl != null)
return bsl;
Integer page = (Integer) metaIndex.get(name);
if (page == null) { return null; }
bsl = new BSkipList(spanSize, this, page.intValue(), key, val, true);
openIndices.put(name, bsl);
return bsl;
}
public BSkipList makeIndex(String name, Serializer key, Serializer val) throws IOException {
if(metaIndex.get(name) != null) { throw new IOException("Index already exists"); }
int page = allocPage();
metaIndex.put(name, new Integer(page));
BSkipList.init(this, page, spanSize);
BSkipList bsl = new BSkipList(spanSize, this, page, key, val, true);
openIndices.put(name, bsl);
return bsl;
}
public void delIndex(String name) throws IOException {
Integer page = (Integer) metaIndex.remove(name);
if (page == null) { return; }
NullBytes nb = new NullBytes();
BSkipList bsl = new BSkipList(spanSize, this, page.intValue(), nb, nb, true);
bsl.delete();
}
/**
* Added I2P
*/
public void closeIndex(String name) {
BSkipList bsl = (BSkipList) openIndices.remove(name);
if (bsl != null)
bsl.flush();
}
/**
* Note (I2P)
* Does NOT close the RAF / RAI.
*/
public void close() throws IOException {
// added I2P
if (metaIndex == null)
return;
metaIndex.close();
metaIndex = null;
Set oi = openIndices.keySet();
Iterator i = oi.iterator();
Object k;
while(i.hasNext()) {
k = i.next();
BSkipList bsl = (BSkipList) openIndices.get(k);
bsl.close();
}
// Unmount.
file.seek(BlockFile.OFFSET_MOUNTED);
file.writeShort(0);
}
}

View File

@ -0,0 +1,89 @@
/*
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.metanotion.io.RandomAccessInterface;
public class FreeListBlock {
public int page;
public int nextPage;
public int len;
public int[] branches = null;
public RandomAccessInterface file;
public FreeListBlock(RandomAccessInterface file, int startPage) throws IOException {
this.file = file;
this.page = startPage;
BlockFile.pageSeek(file, startPage);
nextPage = file.readInt();
len = file.readInt();
if(len > 0) {
branches = new int[len];
for(int i=0;i<len;i++) {
branches[i] = file.readInt();
}
}
}
public void writeBlock() throws IOException {
BlockFile.pageSeek(file, page);
file.writeInt(nextPage);
if(len > 0) {
file.writeInt(len);
for(int i=0;i<len;i++) { file.writeInt(branches[i]); }
} else {
file.writeInt(0);
}
}
public boolean isFull() {
int cells = (int) ((BlockFile.PAGESIZE - 8) / 4);
if(cells - len > 0) { return false; }
return true;
}
public void addPage(int page) {
int[] t = new int[len + 1];
if(len > 0) {
for(int i=0;i<len;i++) { t[i] = branches[i]; }
}
t[len] = page;
len++;
branches = t;
}
public static void initPage(RandomAccessInterface file, int page) throws IOException {
BlockFile.pageSeek(file, page);
file.writeInt(0);
file.writeInt(0);
}
}

View File

@ -0,0 +1,112 @@
/*
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.RandomAccessInterface;
import net.metanotion.io.block.BlockFile;
import net.metanotion.util.skiplist.SkipList;
import net.metanotion.util.skiplist.SkipLevels;
import net.metanotion.util.skiplist.SkipSpan;
public class BSkipLevels extends SkipLevels {
public int levelPage;
public int spanPage;
public BlockFile bf;
protected BSkipLevels() { }
public BSkipLevels(BlockFile bf, int levelPage, BSkipList bsl) throws IOException {
this.levelPage = levelPage;
this.bf = bf;
BlockFile.pageSeek(bf.file, levelPage);
bsl.levelHash.put(new Integer(this.levelPage), this);
int maxLen = bf.file.readShort();
int nonNull = bf.file.readShort();
spanPage = bf.file.readInt();
bottom = (BSkipSpan) bsl.spanHash.get(new Integer(spanPage));
this.levels = new BSkipLevels[maxLen];
int lp;
for(int i=0;i<nonNull;i++) {
lp = bf.file.readInt();
if(lp != 0) {
levels[i] = (BSkipLevels) bsl.levelHash.get(new Integer(lp));
if(levels[i] == null) {
levels[i] = new BSkipLevels(bf, lp, bsl);
bsl.levelHash.put(new Integer(lp), levels[i]);
}
} else {
levels[i] = null;
}
}
}
public static void init(BlockFile bf, int page, int spanPage, int maxHeight) throws IOException {
BlockFile.pageSeek(bf.file, page);
bf.file.writeShort((short) maxHeight);
bf.file.writeShort(0);
bf.file.writeInt(spanPage);
}
public void flush() {
try {
BlockFile.pageSeek(bf.file, levelPage);
bf.file.writeShort((short) levels.length);
int i=0;
for(i=0;i<levels.length;i++) { if(levels[i] == null) { break; } }
bf.file.writeShort(i);
bf.file.writeInt(((BSkipSpan) bottom).page);
for(i=0;i<levels.length;i++) {
if(levels[i]==null) { break; }
bf.file.writeInt(((BSkipLevels) levels[i]).levelPage);
}
} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
}
public void killInstance() {
try {
bf.freePage(levelPage);
} catch (IOException ioe) { throw new RuntimeException("Error freeing database page", ioe); }
}
public SkipLevels newInstance(int levels, SkipSpan ss, SkipList sl) {
try {
BSkipSpan bss = (BSkipSpan) ss;
BSkipList bsl = (BSkipList) sl;
int page = bf.allocPage();
BSkipLevels.init(bf, page, bss.page, levels);
return new BSkipLevels(bf, page, bsl);
} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
}
}

View File

@ -0,0 +1,161 @@
/*
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.HashMap;
import java.util.Random;
import net.metanotion.io.RandomAccessInterface;
import net.metanotion.io.Serializer;
import net.metanotion.io.block.BlockFile;
import net.metanotion.util.skiplist.*;
public class BSkipList extends SkipList {
public int firstSpanPage = 0;
public int firstLevelPage = 0;
public int skipPage = 0;
public BlockFile bf;
public HashMap spanHash = new HashMap();
public HashMap levelHash = new HashMap();
private final boolean fileOnly;
public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer key, Serializer val) throws IOException {
this(spanSize, bf, skipPage, key, val, false);
}
public BSkipList(int spanSize, BlockFile bf, int skipPage, Serializer key, Serializer 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);
firstSpanPage = bf.file.readInt();
firstLevelPage = bf.file.readInt();
size = bf.file.readInt();
spans = bf.file.readInt();
//System.out.println(size + " " + spans);
this.fileOnly = fileOnly;
if (fileOnly)
first = new IBSkipSpan(bf, this, firstSpanPage, key, val);
else
first = new BSkipSpan(bf, this, firstSpanPage, key, val);
stack = new BSkipLevels(bf, firstLevelPage, this);
//rng = new Random(System.currentTimeMillis());
}
public void close() {
//System.out.println("Closing index " + size + " and " + spans);
flush();
first = null;
stack = null;
}
public void flush() {
try {
BlockFile.pageSeek(bf.file, skipPage);
bf.file.writeInt(firstSpanPage);
bf.file.writeInt(firstLevelPage);
bf.file.writeInt(size);
bf.file.writeInt(spans);
} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
}
public void delete() throws IOException {
SkipLevels curLevel = stack, nextLevel;
while(curLevel != null) {
nextLevel = curLevel.levels[0];
curLevel.killInstance();
curLevel = nextLevel;
}
SkipSpan curSpan = first, nextSpan;
while(curSpan != null) {
nextSpan = curSpan.next;
curSpan.killInstance();
curSpan = nextSpan;
}
bf.freePage(skipPage);
}
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.writeInt(firstSpan);
bf.file.writeInt(firstLevel);
bf.file.writeInt(0);
bf.file.writeInt(1);
BSkipSpan.init(bf, firstSpan, spanSize);
BSkipLevels.init(bf, firstLevel, firstSpan, 4);
}
public int maxLevels() {
int max = super.maxLevels();
int cells = (int) ((BlockFile.PAGESIZE - 8) / 4);
return (max > cells) ? cells : max;
}
@Override
public SkipIterator iterator() {
if (!this.fileOnly)
return super.iterator();
return new IBSkipIterator(first, 0);
}
@Override
public SkipIterator min() {
return iterator();
}
@Override
public SkipIterator max() {
if (!this.fileOnly)
return super.max();
SkipSpan ss = stack.getEnd();
return new IBSkipIterator(ss, ss.nKeys - 1);
}
@Override
public SkipIterator find(Comparable key) {
if (!this.fileOnly)
return super.find(key);
int[] search = new int[1];
SkipSpan ss = stack.getSpan(stack.levels.length - 1, key, search);
if(search[0] < 0) { search[0] = -1 * (search[0] + 1); }
return new IBSkipIterator(ss, search[0]);
}
}

View File

@ -0,0 +1,233 @@
/*
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.RandomAccessInterface;
import net.metanotion.io.Serializer;
import net.metanotion.io.block.BlockFile;
import net.metanotion.io.data.NullBytes;
import net.metanotion.util.skiplist.SkipList;
import net.metanotion.util.skiplist.SkipSpan;
public class BSkipSpan extends SkipSpan {
protected BlockFile bf;
protected int page;
protected int overflowPage;
protected int prevPage;
protected int nextPage;
protected Serializer keySer;
protected Serializer valSer;
// I2P
protected int spanSize;
public static void init(BlockFile bf, int page, int spanSize) throws IOException {
BlockFile.pageSeek(bf.file, page);
bf.file.writeInt(0);
bf.file.writeInt(0);
bf.file.writeInt(0);
bf.file.writeShort((short) spanSize);
bf.file.writeShort(0);
}
public SkipSpan newInstance(SkipList sl) {
try {
int newPage = bf.allocPage();
init(bf, newPage, bf.spanSize);
return new BSkipSpan(bf, (BSkipList) sl, newPage, keySer, valSer);
} catch (IOException ioe) { throw new RuntimeException("Error creating database page", ioe); }
}
public void killInstance() {
try {
int curPage = overflowPage;
int next;
while(curPage != 0) {
BlockFile.pageSeek(bf.file, curPage);
next = bf.file.readInt();
bf.freePage(curPage);
curPage = next;
}
bf.freePage(page);
} catch (IOException ioe) { throw new RuntimeException("Error freeing database page", ioe); }
}
public void flush() {
try {
BlockFile.pageSeek(bf.file, page);
bf.file.writeInt(overflowPage);
bf.file.writeInt((prev != null) ? ((BSkipSpan) prev).page : 0);
bf.file.writeInt((next != null) ? ((BSkipSpan) next).page : 0);
bf.file.writeShort((short) keys.length);
bf.file.writeShort((short) nKeys);
int ksz, vsz;
int curPage = this.page;
int[] curNextPage = new int[1];
curNextPage[0] = this.overflowPage;
int[] pageCounter = new int[1];
pageCounter[0] = 16;
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(0);
BlockFile.pageSeek(bf.file, curPage);
bf.file.writeInt(curNextPage[0]);
}
BlockFile.pageSeek(bf.file, curNextPage[0]);
curPage = curNextPage[0];
curNextPage[0] = bf.file.readInt();
pageCounter[0] = 4;
}
keyData = this.keySer.getBytes(keys[i]);
valData = this.valSer.getBytes(vals[i]);
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);
this.overflowPage = bf.file.readInt();
} catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); }
}
private static void load(BSkipSpan bss, BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
loadInit(bss, bf, bsl, spanPage, key, val);
bss.loadData();
}
/**
* I2P - first half of load()
* Only read the span headers
*/
protected static void loadInit(BSkipSpan bss, BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
bss.bf = bf;
bss.page = spanPage;
bss.keySer = key;
bss.valSer = val;
bsl.spanHash.put(new Integer(spanPage), bss);
BlockFile.pageSeek(bf.file, spanPage);
bss.overflowPage = bf.file.readInt();
bss.prevPage = bf.file.readInt();
bss.nextPage = bf.file.readInt();
bss.spanSize = bf.file.readShort();
bss.nKeys = bf.file.readShort();
}
/**
* I2P - second half of load()
* Load the whole span's keys and values into memory
*/
protected void loadData() throws IOException {
this.keys = new Comparable[this.spanSize];
this.vals = 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] = 16;
// 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]);
curPage = curNextPage[0];
curNextPage[0] = this.bf.file.readInt();
pageCounter[0] = 4;
}
ksz = this.bf.file.readShort();
vsz = this.bf.file.readShort();
pageCounter[0] +=4;
byte[] k = new byte[ksz];
byte[] v = new byte[vsz];
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage);
// System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
this.keys[i] = (Comparable) this.keySer.construct(k);
this.vals[i] = this.valSer.construct(v);
}
}
protected BSkipSpan() { }
public BSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
BSkipSpan.load(this, bf, bsl, spanPage, key, val);
this.next = null;
this.prev = null;
BSkipSpan bss = this;
BSkipSpan temp;
int np = nextPage;
while(np != 0) {
temp = (BSkipSpan) bsl.spanHash.get(new Integer(np));
if(temp != null) {
bss.next = temp;
break;
}
bss.next = new BSkipSpan();
bss.next.next = null;
bss.next.prev = bss;
bss = (BSkipSpan) bss.next;
BSkipSpan.load(bss, bf, bsl, np, key, val);
np = bss.nextPage;
}
bss = this;
np = prevPage;
while(np != 0) {
temp = (BSkipSpan) bsl.spanHash.get(new Integer(np));
if(temp != null) {
bss.next = temp;
break;
}
bss.prev = new BSkipSpan();
bss.prev.next = bss;
bss.prev.prev = null;
bss = (BSkipSpan) bss.prev;
BSkipSpan.load(bss, bf, bsl, np, key, val);
np = bss.prevPage;
}
}
}

View File

@ -0,0 +1,137 @@
/*
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 extends SkipIterator {
public IBSkipIterator(SkipSpan ss, int index) {
super(ss, index);
}
/**
* @return the next value, and advances the index
* @throws NoSuchElementException
* @throws RuntimeException on IOE
*/
@Override
public Object next() {
Object 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 Comparable nextKey() {
Comparable c;
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 Object 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];
}
}

View File

@ -0,0 +1,304 @@
/*
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;
/**
* 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 extends BSkipSpan {
private Comparable firstKey;
private static final boolean DEBUG = false;
@Override
public SkipSpan newInstance(SkipList sl) {
if (DEBUG)
System.err.println("Splitting page " + this.page + " containing " + this.nKeys + '/' + this.spanSize);
try {
int newPage = bf.allocPage();
init(bf, newPage, bf.spanSize);
SkipSpan rv = new IBSkipSpan(bf, (BSkipList) sl, newPage, keySer, valSer);
// this is called after a split, so we need the data arrays initialized
rv.keys = new Comparable[bf.spanSize];
rv.vals = 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 = keys[0];
else
this.firstKey = null;
this.keys = null;
this.vals = null;
if (DEBUG)
System.err.println("Flushed data for 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 (DEBUG)
System.err.println("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] = 16;
ksz = this.bf.file.readShort();
this.bf.file.skipBytes(2); //vsz
pageCounter[0] +=4;
byte[] k = new byte[ksz];
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
this.firstKey = (Comparable) this.keySer.construct(k);
if (DEBUG)
System.err.println("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 {
BlockFile.pageSeek(this.bf.file, this.page);
// 3 ints and 2 shorts
this.bf.file.skipBytes(16);
}
/**
* 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 Object getData(Comparable key) throws IOException {
seekData();
int ksz, vsz;
int curPage = this.page;
int[] curNextPage = new int[1];
curNextPage[0] = this.overflowPage;
int[] pageCounter = new int[1];
pageCounter[0] = 16;
//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]);
curPage = curNextPage[0];
curNextPage[0] = this.bf.file.readInt();
pageCounter[0] = 4;
}
ksz = this.bf.file.readShort();
vsz = this.bf.file.readShort();
pageCounter[0] +=4;
byte[] k = new byte[ksz];
byte[] v = new byte[vsz];
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage);
//System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
Comparable ckey = (Comparable) this.keySer.construct(k);
int diff = ckey.compareTo(key);
if (diff == 0) {
//System.err.println("Found " + key + " at " + i + " (first: " + this.firstKey + ')');
return this.valSer.construct(v);
}
if (diff > 0) {
//System.err.println("NOT Found " + key + " at " + i + " (first: " + this.firstKey + " current: " + ckey + ')');
return null;
}
}
//System.err.println("NOT Found " + key + " at end (first: " + this.firstKey + ')');
return null;
}
protected IBSkipSpan() { }
public IBSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
if (DEBUG)
System.err.println("New ibss page " + spanPage);
BSkipSpan.loadInit(this, bf, bsl, spanPage, key, val);
loadFirstKey();
this.next = null;
this.prev = null;
IBSkipSpan bss = this;
IBSkipSpan temp;
int np = nextPage;
while(np != 0) {
temp = (IBSkipSpan) bsl.spanHash.get(new Integer(np));
if(temp != null) {
bss.next = temp;
break;
}
bss.next = new IBSkipSpan();
bss.next.next = null;
bss.next.prev = bss;
bss = (IBSkipSpan) bss.next;
BSkipSpan.loadInit(bss, bf, bsl, np, key, val);
bss.loadFirstKey();
np = bss.nextPage;
}
bss = this;
np = prevPage;
while(np != 0) {
temp = (IBSkipSpan) bsl.spanHash.get(new Integer(np));
if(temp != null) {
bss.next = temp;
break;
}
bss.prev = new IBSkipSpan();
bss.prev.next = bss;
bss.prev.prev = null;
bss = (IBSkipSpan) bss.prev;
BSkipSpan.loadInit(bss, bf, bsl, np, key, val);
bss.loadFirstKey();
np = bss.prevPage;
}
}
/**
* Does not call super, we always store first key here
*/
@Override
public Comparable 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 getSpan(Comparable key, int[] search) {
try {
seekAndLoadData();
} catch (IOException ioe) {
throw new RuntimeException("Error reading database", ioe);
}
SkipSpan 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 Object get(Comparable 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 put(Comparable key, Object val, SkipList sl) {
try {
seekAndLoadData();
} catch (IOException ioe) {
throw new RuntimeException("Error reading database", ioe);
}
SkipSpan 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(Comparable key, SkipList sl) {
try {
seekAndLoadData();
} catch (IOException ioe) {
throw new RuntimeException("Error reading database", ioe);
}
Object[] rv = super.remove(key, sl);
// flush() nulls out the data
return rv;
}
}

View File

@ -0,0 +1,51 @@
/*
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 {
public byte[] getBytes(Object o) {
byte[] b = new byte[4];
int v = ((Integer) 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 Object construct(byte[] b) {
int v = (((int)(b[0] & 0xff) << 24) |
((int)(b[1] & 0xff) << 16) |
((int)(b[2] & 0xff) << 8) |
((int)(b[3] & 0xff)));
return new Integer(v);
}
}

View File

@ -0,0 +1,59 @@
/*
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 LongBytes implements Serializer {
public byte[] getBytes(Object o) {
byte[] b = new byte[8];
long v = ((Long) o).longValue();
b[0] = (byte)(0xff & (v >> 56));
b[1] = (byte)(0xff & (v >> 48));
b[2] = (byte)(0xff & (v >> 40));
b[3] = (byte)(0xff & (v >> 32));
b[4] = (byte)(0xff & (v >> 24));
b[5] = (byte)(0xff & (v >> 16));
b[6] = (byte)(0xff & (v >> 8));
b[7] = (byte)(0xff & v);
return b;
}
public Object construct(byte[] b) {
long v =(((long)(b[0] & 0xff) << 56) |
((long)(b[1] & 0xff) << 48) |
((long)(b[2] & 0xff) << 40) |
((long)(b[3] & 0xff) << 32) |
((long)(b[4] & 0xff) << 24) |
((long)(b[5] & 0xff) << 16) |
((long)(b[6] & 0xff) << 8) |
((long)(b[7] & 0xff)));
return new Long(v);
}
}

View File

@ -0,0 +1,36 @@
/*
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 NullBytes implements Serializer {
public byte[] getBytes(Object o) { return null; }
public Object construct(byte[] b) { return null; }
}

View File

@ -0,0 +1,47 @@
/*
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 {
public byte[] getBytes(Object o) {
try {
return ((String) o).getBytes("US-ASCII");
} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
}
public Object construct(byte[] b) {
try {
return new String(b, "US-ASCII");
} catch (UnsupportedEncodingException uee) { throw new Error("Unsupported Encoding"); }
}
}

View File

@ -0,0 +1,156 @@
<!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>
<h1>Metanotion BlockFile Database</h1>
<p>A 100% Java 1.3, BSD Licensed, embeddable single file database engine in 32KB. This database was designed for PDA based and J2ME applications.</p>
<h2>Table of Contents</h2>
<ul>
<li><a href="#features">Features</a></li>
<li><a href="#unfeatures">Unfeatures</a></li>
<li><a href="#future">Future Plans</a></li>
<li><a href="#design">What kind of database is this?</a></li>
<li><a href="#examples">Examples and API</a></li>
<li><a href="#download">Download</a></li>
</ul>
<a name="features"><h2>Features</h2></a>
<ul>
<li>100% Java 1.3. No JNI.</li>
<li>Will work with any "file" as long as you can approximate something like <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/RandomAccessFile.html">java.io.RandomAccessFile</a>, you can use this.</li>
<li>BSD Licensed. Yes, this means you can use it for free in a commercial project. However, if you base some really cool mobile technology startup on this code we'll gladly accept stock options...</p>
<li>No dependence on file API's(useful for mobile apps)</li>
<li>Small. 32KB in a JAR file. &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>
<a name="unfeatures"><h2>Unfeatures</h2></a>
<p>A good, ACID database is a nice thing to work with. Unfortunately, in the goal to make this small, fast, and work with minimal dependencies, something had to give. So I list things which this database will likely never have. Of course, since it is BSD Licensed, patches welcome...</p>
<ul>
<li>No transactions.</li>
<li>No SQL.</li>
<li>No JDBC.</li>
<li>No use of reflection or automagical serialization tricks.</li>
</ul>
<a name="future"><h2>Future Plans</h2></a>
<p>There are still bugs(none known...). The app that this was written for is still in testing, but we should most of the issues sorted by the time we deploy it in a few weeks(early November, 2006). Some loading speed issues on large record sets, and memory usage could still be improved. All this and feedback from other uses will direct this products evolution.</p>
<p>What is currently up here is not "1.0" code, but we will release a labeled "1.0" version once we feel happy with the state of the codebase.</p>
<a name="design"><h2>What KIND of database is this?</h2></a>
<p>You probably store at least part of your application data in memory in a class from the <a href="http://java.sun.com/j2se/1.4.2/docs/guide/collections/">Java Collections Framework</a>. The BlockFile database stores data in a <a href="http://en.wikipedia.org/wiki/Skip_list">Skip</a> <a href="http://eternallyconfuzzled.com/tuts/skip.html">List</a> that almost implements <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/SortedMap.html">java.util.SortedMap</a>. You can create and store as many named(with a string) SkipList in the database as you want.</p>
<p>To serialize your data, you have to either extend our SerialStreams class or implement our Serializer interface. We could have done something cool and fancy with reflection(and other cool stuff with Java 1.5), but that would probably not do the Right Thing&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>
</p>
<a name="examples"><h2>Examples</h2></a>
<p>Better documentation is forthcoming, but there really isn't much to know. The entire public interface to the library is on this page. Where possible, it sticks to idiomatic Java and standard interfaces.</p>
<ul>
<li>Open a database:
<pre>
import net.metanotion.io.block.BlockFile;
...
try {
BlockFile db = new BlockFile(new File("my.db"), false); // true will create
} catch (IOException ioe) {
System.out.println("Bummer");
}
</pre>
<li>
<li>Load or Create a SkipList:
<pre>
import net.metanotion.util.skiplist.SkipList;
import net.metanotion.io.Serializer;
...
class KeySerializer implements Serializer ...
class ValueSerializer implements Serializer ...
...
// Open preexisting
SkipList index = db.getIndex("My Index", new KeySerializer(), new ValueSerializer());
// Create
SkipList index = db.makeIndex("My Index", new KeySerializer(), new ValueSerializer());
</pre>
</li>
</ul>
<h3>net.metanotion.io.block.BlockFile</h3>
All the public interface methods:
<pre>
public class BlockFile implements Closeable {
public BlockFile(RandomAccessInterface rai) ...
public BlockFile(RandomAccessFile raf) ...
public BlockFile(RandomAccessFile raf, boolean init) ...
public BlockFile(File f, boolean init) ...
public BlockFile(RandomAccessInterface rai, boolean init) ...
public SkipList getIndex(String name, Serializer key, Serializer val) ...
public SkipList makeIndex(String name, Serializer key, Serializer val) ...
public void delIndex(String name) ...
public void close() ...
}
</pre>
<h3>What's this "net.metanotion.io.RandomAccessInterface"?</h3>
<p>Basically, its an interface version of <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/RandomAccessFile.html">java.io.RandomAccessFile</a>(which itself implements <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/DataInput.html">DataInput</a>, <a href="http://java.sun.com/j2se/1.3/docs/api/java/io/DataOutput.html">DataOutput</a> and a few methods for getting/setting the file pointer).</p>
<p>So, in other words, if you can provide an implementation of this interface, you can use the BlockFile database. This frees it from dependence on the RandomAccessFile class. If you don't see why this is useful and you're going to be using "files" on PDA's and phone's, well, you'll understand soon enough...</p>
<a name="download"><h2>Download</h2></a>
<h3>Bugfix and cleanup Release 10/6/2006</h2>
<p>An unnecessary class was removed, some junk methods removed, and a couple of JDK compatability issues were fixed. The StringBytes class was switched to ASCII(from UTF-8) for better compatibility.</p>
<ul>
<li><a href="BlockFile.2006.10.06.jar">BlockFile binary JAR, version 0.1.1</a></li>
<li><a href="BlockFile.src.2006.10.06.zip">BlockFile source code</a></li>
</ul>
<h3>Initial Release 9/28/2006</h3>
<ul>
<li><a href="BlockFile.2006.09.28.jar">BlockFile binary JAR, version 0.1</a></li>
<li><a href="BlockFile.src.2006.09.28.zip">BlockFile source code</a></li>
</ul>
<hr />
<center>&copy; 2006 <a href="http://www.metanotion.net/">Metanotion Software</a></center>
</body>
</html>

View File

@ -0,0 +1,121 @@
/*
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 implements ListIterator {
protected SkipSpan ss;
protected int index;
protected SkipIterator() { }
public SkipIterator(SkipSpan 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 Object next() {
Object 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 Comparable nextKey() {
Comparable c;
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 Object 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(Object o) { throw new UnsupportedOperationException(); }
public void remove() { throw new UnsupportedOperationException(); }
public void set(Object o) { throw new UnsupportedOperationException(); }
public int nextIndex() { throw new UnsupportedOperationException(); }
public int previousIndex() { throw new UnsupportedOperationException(); }
}

View File

@ -0,0 +1,168 @@
/*
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;
public class SkipLevels {
/* "Next" pointers
The highest indexed level is the "highest" level in the list.
The "bottom" level is the direct pointer to a SkipSpan.
*/
public SkipLevels[] levels;
public SkipSpan bottom;
public SkipLevels newInstance(int levels, SkipSpan ss, SkipList sl) { return new SkipLevels(levels, ss); }
public void killInstance() { }
public void flush() { }
protected SkipLevels() { }
public SkipLevels(int size, SkipSpan span) {
if(size < 1) { throw new Error("Invalid Level Skip size"); }
levels = new SkipLevels[size];
bottom = span;
}
public void print() {
SkipLevels prev = null;
SkipLevels max = null;
System.out.print("SL:" + key() + "::");
for(int i=0;i<levels.length;i++) {
if(levels[i] != null) {
max = levels[i];
System.out.print(i + "->" + levels[i].key() + " ");
} else {
System.out.print(i + "->() ");
}
}
System.out.print("\n");
if(levels[0] != null) {
levels[0].print();
}
}
public SkipSpan getEnd() {
for(int i=(levels.length - 1);i>=0;i--) {
if(levels[i] != null) { return levels[i].getEnd(); }
}
return bottom.getEnd();
}
public SkipSpan getSpan(int start, Comparable 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 Comparable key() { return bottom.firstKey(); }
public Object get(int start, Comparable 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);
}
public Object[] remove(int start, Comparable key, SkipList sl) {
SkipSpan ss = null;
Object[] res = null;
SkipLevels 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) 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 {
res[1] = null;
}
}
if((bottom.nKeys == 0) && (sl.first != bottom)) { this.killInstance(); }
return res;
}
public SkipLevels put(int start, Comparable key, Object val, SkipList sl) {
SkipSpan ss = null;
SkipLevels slvls = null;
for(int i=Math.min(start, levels.length - 1);i>=0;i--) {
if((levels[i] != null) && (levels[i].key().compareTo(key) <= 0)) {
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++) {
slvls.levels[j] = levels[j];
levels[j] = slvls;
}
if(levels.length < slvls.levels.length) {
this.flush();
return slvls;
}
}
this.flush();
return null;
}
}
ss = bottom.put(key,val,sl);
if(ss!=null) {
int height = sl.generateColHeight();
if(height != 0) {
slvls = this.newInstance(height, ss, sl);
for(int i=0;i<(Math.min(height,levels.length));i++) {
slvls.levels[i] = levels[i];
levels[i] = slvls;
}
}
this.flush();
if(levels.length >= height) { return null; }
return slvls;
} else {
return null;
}
}
}

View File

@ -0,0 +1,321 @@
/*
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.Random;
import net.i2p.util.RandomSource;
public class SkipList {
protected SkipSpan first;
protected SkipLevels stack;
// I2P mod
public static final Random rng = RandomSource.getInstance();
public int size=0;
public int spans=0;
public void flush() { }
protected SkipList() { }
public SkipList(int span) {
if(span < 1) { throw new Error("Span size too small"); }
first = new SkipSpan(span);
stack = new SkipLevels(1, first);
spans = 1;
//rng = new Random(System.currentTimeMillis());
}
public int size() { return size; }
public int maxLevels() {
int hob = 0, s = spans;
while(spans > 0) {
hob++;
spans = spans / 2;
}
return (hob > 4) ? hob : 4;
}
public int generateColHeight() {
int bits = rng.nextInt();
boolean cont = true;
int res=0;
for(res=0; cont; res++) {
cont = ((bits % 2) == 0) ? true : false;
bits = bits / 2;
}
return Math.max(0, Math.min(res, maxLevels()));
}
public void put(Comparable key, Object val) {
if(key == null) { throw new NullPointerException(); }
if(val == null) { throw new NullPointerException(); }
SkipLevels slvls = stack.put(stack.levels.length - 1, key, val, this);
if(slvls != null) {
SkipLevels[] levels = 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();
}
}
public Object remove(Comparable 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 slvls = (SkipLevels) 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 res[0];
}
return null;
}
public void printSL() {
System.out.println("List size " + size + " spans " + spans);
stack.print();
}
public void print() {
System.out.println("List size " + size + " spans " + spans);
first.print();
}
public Object get(Comparable key) {
if(key == null) { throw new NullPointerException(); }
return stack.get(stack.levels.length - 1, key);
}
public SkipIterator iterator() { return new SkipIterator(first, 0); }
public SkipIterator min() { return new SkipIterator(first, 0); }
public SkipIterator max() {
SkipSpan ss = stack.getEnd();
return new SkipIterator(ss, ss.nKeys - 1);
}
/** @return an iterator where nextKey() is the first one greater than or equal to 'key' */
public SkipIterator find(Comparable key) {
int[] search = new int[1];
SkipSpan ss = stack.getSpan(stack.levels.length - 1, key, search);
if(search[0] < 0) { search[0] = -1 * (search[0] + 1); }
return new SkipIterator(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());
}
}
*****/
}

View File

@ -0,0 +1,272 @@
/*
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;
public class SkipSpan {
public int nKeys = 0;
public Comparable[] keys;
public Object[] vals;
public SkipSpan next, prev;
public SkipSpan newInstance(SkipList sl) { return new SkipSpan(keys.length); }
public void killInstance() { }
public void flush() { }
protected SkipSpan() { }
public SkipSpan(int size) {
keys = new Comparable[size];
vals = new Object[size];
}
public void print() {
System.out.println("Span");
for(int i=0;i<nKeys;i++) {
System.out.println("\t" + keys[i] + " => " + vals[i]);
}
if(next != null) { next.print(); }
}
private int binarySearch(Comparable 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 getEnd() {
if(next == null) { return this; }
return next.getEnd();
}
public SkipSpan getSpan(Comparable 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 Object get(Comparable 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, Comparable key, Object val, SkipList sl) {
SkipSpan right = newInstance(sl);
sl.spans++;
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();
}
private SkipSpan insert(int loc, Comparable key, Object val, SkipList sl) {
sl.size++;
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;
}
}
public SkipSpan put(Comparable key, Object val, SkipList sl) {
if(nKeys == 0) {
sl.size++;
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;
}
}
public Object[] remove(Comparable key, SkipList 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.size--;
if(nKeys == 1) {
if(sl.spans > 1) { sl.spans--; }
if((this.prev == null) && (this.next != null)) {
res[1] = this.next;
// We're the first node in the list...
for(int i=0;i<next.nKeys;i++) {
keys[i] = next.keys[i];
vals[i] = next.vals[i];
}
nKeys = next.nKeys;
SkipSpan nn = next.next;
next.killInstance();
this.flush();
this.next = nn;
} else {
res[1] = this;
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;
this.prev = null;
nKeys = 0;
this.killInstance();
}
} else {
pushTogether(loc);
this.flush();
}
return res;
}
/** I2P */
public Comparable firstKey() {
return keys[0];
}
}

View File

@ -0,0 +1,26 @@
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.