mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-04 01:50:38 +00:00
620 lines
16 KiB
Java
620 lines
16 KiB
Java
/* Copyright (C) 2004, 2005 Free Software Foundation
|
|
|
|
This file is part of libgcj.
|
|
|
|
This software is copyrighted work licensed under the terms of the
|
|
Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
|
|
details. */
|
|
|
|
|
|
|
|
/* A PersistentByteMap maps a byte array to another byte array. It
|
|
uses a file that does not need to be serialized but may be
|
|
memory-mapped and read in-place. So, even if there are many instances
|
|
of gcj applications running, they can share PersistentByteMaps.
|
|
|
|
The idea is to make searches as fast as possible: opening a
|
|
PersistentByteMap is cheap and search time doesn't grow with the
|
|
number of entries in the table. On the other hand, enumerating the
|
|
map is slow, but that is a relatively uncommon operation.
|
|
|
|
The main use of this class is to provide a way to map the
|
|
MessageDigest of a class file to the location of a DSO that contains
|
|
the compiled version of that class. It is up the the installer of an
|
|
application to keep the DSO up to date with the jar.
|
|
|
|
USAGE:
|
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
digest = md.digest(bytes);
|
|
|
|
PersistentByteMap map
|
|
= new PersistentByteMap
|
|
(fileName, PersistentByteMap.AccessMode.READ_ONLY);
|
|
|
|
byte[] soName = map.get(digest);
|
|
if (soName)
|
|
{
|
|
String SharedLibraryName = new String(soName);
|
|
|
|
BUGS/FEATURES:
|
|
remove() isn't written yet.
|
|
|
|
capacity is fixed once the map has been created.
|
|
|
|
We use linear probing to resolve collisions. It might be
|
|
better to use a scheme that results in fewer probes to
|
|
determine that an item isn't found. However, even when the
|
|
table is half full there are only on average 1.5 probes for a
|
|
successful search and 2.5 probes for an unsuccessful one.
|
|
|
|
We don't do any locking at all: adding to a PersistentByteMap
|
|
at runtime is possible, but it requires filesystem locks
|
|
around get(), put(), and remove().
|
|
*/
|
|
|
|
package gnu.gcj.runtime;
|
|
|
|
import java.io.*;
|
|
import java.nio.*;
|
|
import java.nio.channels.*;
|
|
import java.util.*;
|
|
import java.security.MessageDigest;
|
|
import java.math.BigInteger;
|
|
|
|
public class PersistentByteMap
|
|
{
|
|
private MappedByteBuffer buf;
|
|
|
|
static private final int MAGIC = 0;
|
|
static private final int VERSION = 4;
|
|
static private final int CAPACITY = 8;
|
|
static private final int TABLE_BASE = 12;
|
|
static private final int STRING_BASE = 16;
|
|
static private final int STRING_SIZE = 20;
|
|
static private final int FILE_SIZE = 24;
|
|
static private final int ELEMENTS = 28;
|
|
|
|
static private final int INT_SIZE = 4;
|
|
|
|
static private final int TABLE_ENTRY_SIZE = 2 * INT_SIZE;
|
|
|
|
private int capacity; // number of entries
|
|
private int table_base; // offset from start of file, in bytes
|
|
private int string_base; // offset from start of file, in bytes
|
|
private int string_size; // size of string table, in bytes
|
|
private int file_size; // size of file, in bytes;
|
|
private int elements; // number of elements in table
|
|
|
|
private long length; // the length of the underlying file
|
|
|
|
private final File name; // The name of the underlying file
|
|
|
|
static private final int UNUSED_ENTRY = -1;
|
|
|
|
static public final int KEYS = 0;
|
|
static public final int VALUES = 1;
|
|
static public final int ENTRIES = 2;
|
|
|
|
private HashMap values; // A map of strings in the string table.
|
|
|
|
FileChannel fc; // The underlying file channel.
|
|
|
|
static final public class AccessMode
|
|
{
|
|
private final FileChannel.MapMode mapMode;
|
|
|
|
static
|
|
{
|
|
READ_ONLY = new AccessMode(FileChannel.MapMode.READ_ONLY);
|
|
READ_WRITE = new AccessMode(FileChannel.MapMode.READ_WRITE);
|
|
PRIVATE = new AccessMode(FileChannel.MapMode.PRIVATE);
|
|
}
|
|
|
|
public static final AccessMode READ_ONLY;
|
|
public static final AccessMode READ_WRITE;
|
|
public static final AccessMode PRIVATE;
|
|
|
|
private AccessMode(FileChannel.MapMode mode)
|
|
{
|
|
this.mapMode = mode;
|
|
}
|
|
}
|
|
|
|
private PersistentByteMap(File name)
|
|
{
|
|
this.name = name;
|
|
}
|
|
|
|
public PersistentByteMap(String filename, AccessMode mode)
|
|
throws IOException
|
|
{
|
|
this(new File(filename), mode);
|
|
}
|
|
|
|
public PersistentByteMap(File f, AccessMode mode)
|
|
throws IOException
|
|
{
|
|
name = f;
|
|
|
|
if (mode == AccessMode.READ_ONLY)
|
|
{
|
|
FileInputStream fis = new FileInputStream(f);
|
|
fc = fis.getChannel();
|
|
}
|
|
else
|
|
{
|
|
RandomAccessFile fos = new RandomAccessFile(f, "rw");
|
|
fc = fos.getChannel();
|
|
}
|
|
|
|
length = fc.size();
|
|
buf = fc.map(mode.mapMode, 0, length);
|
|
|
|
int magic = getWord (MAGIC);
|
|
if (magic != 0x67636a64) /* "gcjd" */
|
|
throw new IllegalArgumentException(f.getName());
|
|
|
|
table_base = getWord (TABLE_BASE);
|
|
capacity = getWord (CAPACITY);
|
|
string_base = getWord (STRING_BASE);
|
|
string_size = getWord (STRING_SIZE);
|
|
file_size = getWord (FILE_SIZE);
|
|
elements = getWord (ELEMENTS);
|
|
|
|
// FIXME: Insert a bunch of sanity checks here
|
|
}
|
|
|
|
private void init (PersistentByteMap m, File f, int capacity, int strtabSize)
|
|
throws IOException
|
|
{
|
|
f.createNewFile();
|
|
RandomAccessFile raf = new RandomAccessFile(f, "rw");
|
|
|
|
{
|
|
// The user has explicitly provided a size for the table.
|
|
// We're going to make that size prime. This isn't
|
|
// strictly necessary but it can't hurt.
|
|
//
|
|
// We expand the size by 3/2 and round the result because the
|
|
// hash table is intolerably slow when more than 2/3 full.
|
|
|
|
BigInteger size = new BigInteger(Integer.toString(((capacity*3)+1)/2));
|
|
BigInteger two = BigInteger.ONE.add(BigInteger.ONE);
|
|
|
|
if (size.getLowestSetBit() != 0) // A hard way to say isEven()
|
|
size = size.add(BigInteger.ONE);
|
|
|
|
while (! size.isProbablePrime(10))
|
|
size = size.add(two);
|
|
|
|
this.capacity = capacity = size.intValue();
|
|
}
|
|
|
|
table_base = 64;
|
|
string_base = table_base + capacity * TABLE_ENTRY_SIZE;
|
|
string_size = 0;
|
|
file_size = string_base;
|
|
elements = 0;
|
|
|
|
int totalFileSize = string_base + strtabSize;
|
|
|
|
// Create the file; this rounds up the size of the file to a fixed
|
|
// number of 4k pages.
|
|
byte[] _4k = new byte[4096];
|
|
for (long i = 0; i < totalFileSize; i+= 4096)
|
|
raf.write(_4k);
|
|
|
|
fc = raf.getChannel();
|
|
buf = fc.map(FileChannel.MapMode.READ_WRITE, 0, raf.length());
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
putKeyPos(UNUSED_ENTRY, i);
|
|
|
|
putWord(0x67636a64, MAGIC);
|
|
putWord(0x01, VERSION);
|
|
putWord(capacity, CAPACITY);
|
|
putWord(table_base, TABLE_BASE);
|
|
putWord(string_base, STRING_BASE);
|
|
putWord(file_size, FILE_SIZE);
|
|
putWord(elements, ELEMENTS);
|
|
buf.force();
|
|
|
|
length = fc.size();
|
|
string_size = 0;
|
|
}
|
|
|
|
static public PersistentByteMap
|
|
emptyPersistentByteMap(File name, int capacity, int strtabSize)
|
|
throws IOException
|
|
{
|
|
PersistentByteMap m = new PersistentByteMap(name);
|
|
m.init(m, name, capacity, strtabSize);
|
|
return m;
|
|
}
|
|
|
|
private int getWord (int index)
|
|
{
|
|
buf.position(index);
|
|
byte[] wordBuf = new byte[4];
|
|
buf.get(wordBuf);
|
|
|
|
int result = (int)wordBuf[0]&0xff;
|
|
result += ((int)wordBuf[1]&0xff) << 8;
|
|
result += ((int)wordBuf[2]&0xff) << 16;
|
|
result += ((int)wordBuf[3]&0xff) << 24;
|
|
return result;
|
|
}
|
|
|
|
private void putWord (int word, int index)
|
|
{
|
|
buf.position(index);
|
|
byte[] wordBuf = new byte[4];
|
|
wordBuf[0] = (byte)(word);
|
|
wordBuf[1] = (byte)(word >>> 8);
|
|
wordBuf[2] = (byte)(word >>> 16);
|
|
wordBuf[3] = (byte)(word >>> 24);
|
|
buf.put(wordBuf);
|
|
}
|
|
|
|
public Set entrySet()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private int getBucket(int n)
|
|
{
|
|
return table_base + (2*n * INT_SIZE);
|
|
}
|
|
|
|
private int getKeyPos(int n)
|
|
{
|
|
return getWord(getBucket(n));
|
|
}
|
|
|
|
private int getValuePos(int n)
|
|
{
|
|
return getWord(getBucket(n) + INT_SIZE);
|
|
}
|
|
|
|
private void putKeyPos(int index, int n)
|
|
{
|
|
putWord(index, getBucket(n));
|
|
}
|
|
|
|
private void putValuePos(int index, int n)
|
|
{
|
|
putWord(index, getBucket(n) + INT_SIZE);
|
|
}
|
|
|
|
private byte[] getBytes(int n)
|
|
{
|
|
int len = getWord (string_base + n);
|
|
int base = string_base + n + INT_SIZE;
|
|
byte[] key = new byte[len];
|
|
buf.position(base);
|
|
buf.get(key, 0, len);
|
|
return key;
|
|
}
|
|
|
|
private int hash (byte[] b)
|
|
{
|
|
// We assume that the message digest is evenly distributed, so we
|
|
// only need to use a few bytes of it as the hash function.
|
|
long hashIndex
|
|
= ((b[0]&0xffL)
|
|
+ ((b[1]&0xffL)<<8)
|
|
+ ((b[2]&0xffL)<<16)
|
|
+ ((b[3]&0xffL)<<24));
|
|
long result = hashIndex % (long)capacity;
|
|
return (int)result;
|
|
}
|
|
|
|
public byte[] get(byte[] digest)
|
|
{
|
|
int hashIndex = hash(digest);
|
|
|
|
do
|
|
{
|
|
int k = getKeyPos(hashIndex);
|
|
if (k == UNUSED_ENTRY)
|
|
return null;
|
|
|
|
if (Arrays.equals ((byte[])digest, getBytes(k)))
|
|
return getBytes(getValuePos(hashIndex));
|
|
|
|
// Use linear probing to resolve hash collisions. This may
|
|
// not be theoretically as good as open addressing, but it has
|
|
// good cache behviour.
|
|
hashIndex++;
|
|
hashIndex %= capacity;
|
|
}
|
|
while (true);
|
|
}
|
|
|
|
public void put(byte[] digest, byte[] value)
|
|
throws IllegalAccessException
|
|
{
|
|
int hashIndex = hash(digest);
|
|
|
|
if (elements >= capacity())
|
|
throw new IllegalAccessException("Table Full: " + elements);
|
|
|
|
do
|
|
{
|
|
int k = getKeyPos(hashIndex);
|
|
if (k == UNUSED_ENTRY)
|
|
{
|
|
int newKey = addBytes(digest);
|
|
putKeyPos(newKey, hashIndex);
|
|
int newValue = addBytes(value);
|
|
putValuePos(newValue, hashIndex);
|
|
elements++;
|
|
putWord(elements, ELEMENTS);
|
|
return;
|
|
}
|
|
else if (Arrays.equals (digest, getBytes(k)))
|
|
{
|
|
int newValue = addBytes((byte[])value);
|
|
putValuePos(newValue, hashIndex);
|
|
return;
|
|
}
|
|
|
|
hashIndex++;
|
|
hashIndex %= capacity;
|
|
}
|
|
while (true);
|
|
}
|
|
|
|
private int addBytes (byte[] data)
|
|
throws IllegalAccessException
|
|
{
|
|
if (data.length > 16)
|
|
{
|
|
// Keep track of long strings in the hope that we will be able
|
|
// to re-use them.
|
|
if (values == null)
|
|
{
|
|
values = new HashMap();
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
if (getKeyPos(i) != UNUSED_ENTRY)
|
|
{
|
|
int pos = getValuePos(i);
|
|
ByteWrapper bytes = new ByteWrapper(getBytes(pos));
|
|
values.put(bytes, new Integer(pos));
|
|
}
|
|
}
|
|
|
|
{
|
|
Object result = values.get(new ByteWrapper(data));
|
|
if (result != null)
|
|
{
|
|
// We already have this value in the string table
|
|
return ((Integer)result).intValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.length + INT_SIZE >= this.length)
|
|
throw new IllegalAccessException("String table Full");
|
|
|
|
int extent = string_base+string_size;
|
|
int top = extent;
|
|
putWord(data.length, extent);
|
|
extent += INT_SIZE;
|
|
buf.position(extent);
|
|
buf.put(data, 0, data.length);
|
|
extent += data.length;
|
|
extent += INT_SIZE-1;
|
|
extent &= ~(INT_SIZE-1); // align
|
|
string_size = extent - string_base;
|
|
file_size = extent;
|
|
putWord (string_size, STRING_SIZE);
|
|
putWord (file_size, FILE_SIZE);
|
|
|
|
if (data.length > 16)
|
|
values.put(new ByteWrapper(data), new Integer(top - string_base));
|
|
|
|
return top - string_base;
|
|
}
|
|
|
|
public Iterator iterator(int type)
|
|
{
|
|
return new HashIterator(type);
|
|
}
|
|
|
|
public int size()
|
|
{
|
|
return elements;
|
|
}
|
|
|
|
public int stringTableSize()
|
|
{
|
|
return string_size;
|
|
}
|
|
|
|
public int capacity()
|
|
{
|
|
// With the the table 2/3 full there will be on average 2 probes
|
|
// for a successful search and 5 probes for an unsuccessful one.
|
|
return capacity * 2/3;
|
|
}
|
|
|
|
public void force()
|
|
{
|
|
buf.force();
|
|
}
|
|
|
|
public File getFile()
|
|
{
|
|
return name;
|
|
}
|
|
|
|
// Close the map. Once this has been done, the map can no longer be
|
|
// used.
|
|
public void close() throws IOException
|
|
{
|
|
force();
|
|
fc.close();
|
|
}
|
|
|
|
public void
|
|
putAll(PersistentByteMap t)
|
|
throws IllegalAccessException
|
|
{
|
|
// We can use a fast copy if the size of a map has not changed.
|
|
if (this.elements == 0 && t.capacity == this.capacity
|
|
&& t.length == this.length)
|
|
{
|
|
this.buf.position(0);
|
|
t.buf.position(0);
|
|
this.buf.put(t.buf);
|
|
this.table_base = t.table_base;
|
|
this.string_base = t.string_base;
|
|
this.string_size = t.string_size;
|
|
this.file_size = t.file_size;
|
|
this.elements = t.elements;
|
|
if (t.values != null)
|
|
this.values = (HashMap)t.values.clone();
|
|
return;
|
|
}
|
|
|
|
// Otherwise do it the hard way.
|
|
Iterator iterator = t.iterator(PersistentByteMap.ENTRIES);
|
|
while (iterator.hasNext())
|
|
{
|
|
PersistentByteMap.MapEntry entry
|
|
= (PersistentByteMap.MapEntry)iterator.next();
|
|
this.put((byte[])entry.getKey(), (byte[])entry.getValue());
|
|
}
|
|
}
|
|
|
|
|
|
private final class HashIterator implements Iterator
|
|
{
|
|
/** Current index in the physical hash table. */
|
|
|
|
private int idx;
|
|
private int count;
|
|
private final int type;
|
|
|
|
/**
|
|
* Construct a new HashIterator with the supplied type.
|
|
* @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES}
|
|
*/
|
|
HashIterator(int type)
|
|
{
|
|
this.type = type;
|
|
count = elements;
|
|
idx = 0;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the Iterator has more elements.
|
|
* @return true if there are more elements
|
|
* @throws ConcurrentModificationException if the HashMap was modified
|
|
*/
|
|
public boolean hasNext()
|
|
{
|
|
return count > 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the next element in the Iterator's sequential view.
|
|
* @return the next element
|
|
* @throws ConcurrentModificationException if the HashMap was modified
|
|
* @throws NoSuchElementException if there is none
|
|
*/
|
|
public Object next()
|
|
{
|
|
count--;
|
|
for (int i = idx; i < capacity; i++)
|
|
if (getKeyPos(i) != UNUSED_ENTRY)
|
|
{
|
|
idx = i+1;
|
|
if (type == VALUES)
|
|
return getBytes(getValuePos(i));
|
|
if (type == KEYS)
|
|
return getBytes(getKeyPos(i));
|
|
return new MapEntry(i,
|
|
getBytes(getKeyPos(i)),
|
|
getBytes(getValuePos(i)));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Remove from the underlying collection the last element returned
|
|
* by next (optional operation). This method can be called only
|
|
* once after each call to <code>next()</code>. It does not affect
|
|
* what will be returned by subsequent calls to next.
|
|
*
|
|
* @throws IllegalStateException if next has not yet been called
|
|
* or remove has already been called since the last call
|
|
* to next.
|
|
* @throws UnsupportedOperationException if this Iterator does not
|
|
* support the remove operation.
|
|
*/
|
|
public void remove()
|
|
{
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
}
|
|
|
|
static public final class MapEntry
|
|
{
|
|
private final Object key;
|
|
private final Object value;
|
|
private final int bucket;
|
|
|
|
public MapEntry(int bucket, Object newKey, Object newValue)
|
|
{
|
|
this.key = newKey;
|
|
this.value = newValue;
|
|
this.bucket = bucket;
|
|
}
|
|
|
|
public final Object getKey()
|
|
{
|
|
return key;
|
|
}
|
|
|
|
public final Object getValue()
|
|
{
|
|
return value;
|
|
}
|
|
|
|
public final int getBucket()
|
|
{
|
|
return bucket;
|
|
}
|
|
}
|
|
|
|
// A wrapper class for a byte array that allows collections to be
|
|
// made.
|
|
private final class ByteWrapper
|
|
{
|
|
final byte[] bytes;
|
|
final int hash;
|
|
|
|
public ByteWrapper (byte[] bytes)
|
|
{
|
|
int sum = 0;
|
|
this.bytes = bytes;
|
|
for (int i = 0; i < bytes.length; i++)
|
|
sum += bytes[i];
|
|
hash = sum;
|
|
}
|
|
|
|
public int hashCode()
|
|
{
|
|
return hash;
|
|
}
|
|
|
|
public boolean equals(Object obj)
|
|
{
|
|
return Arrays.equals(bytes, ((ByteWrapper)obj).bytes);
|
|
}
|
|
}
|
|
}
|