1 /* Copyright (C) 2004, 2005 Free Software Foundation 2 3 This file is part of libgcj. 4 5 This software is copyrighted work licensed under the terms of the 6 Libgcj License. Please consult the file "LIBGCJ_LICENSE" for 7 details. */ 8 9 10 11 /* A PersistentByteMap maps a byte array to another byte array. It 12 uses a file that does not need to be serialized but may be 13 memory-mapped and read in-place. So, even if there are many instances 14 of gcj applications running, they can share PersistentByteMaps. 15 16 The idea is to make searches as fast as possible: opening a 17 PersistentByteMap is cheap and search time doesn't grow with the 18 number of entries in the table. On the other hand, enumerating the 19 map is slow, but that is a relatively uncommon operation. 20 21 The main use of this class is to provide a way to map the 22 MessageDigest of a class file to the location of a DSO that contains 23 the compiled version of that class. It is up the the installer of an 24 application to keep the DSO up to date with the jar. 25 26 USAGE: 27 MessageDigest md = MessageDigest.getInstance("MD5"); 28 digest = md.digest(bytes); 29 30 PersistentByteMap map 31 = new PersistentByteMap 32 (fileName, PersistentByteMap.AccessMode.READ_ONLY); 33 34 byte[] soName = map.get(digest); 35 if (soName) 36 { 37 String SharedLibraryName = new String(soName); 38 39 BUGS/FEATURES: 40 remove() isn't written yet. 41 42 capacity is fixed once the map has been created. 43 44 We use linear probing to resolve collisions. It might be 45 better to use a scheme that results in fewer probes to 46 determine that an item isn't found. However, even when the 47 table is half full there are only on average 1.5 probes for a 48 successful search and 2.5 probes for an unsuccessful one. 49 50 We don't do any locking at all: adding to a PersistentByteMap 51 at runtime is possible, but it requires filesystem locks 52 around get(), put(), and remove(). 53 */ 54 55 package gnu.gcj.runtime; 56 57 import java.io.*; 58 import java.nio.*; 59 import java.nio.channels.*; 60 import java.util.*; 61 import java.security.MessageDigest; 62 import java.math.BigInteger; 63 64 public class PersistentByteMap 65 { 66 private MappedByteBuffer buf; 67 68 static private final int MAGIC = 0; 69 static private final int VERSION = 4; 70 static private final int CAPACITY = 8; 71 static private final int TABLE_BASE = 12; 72 static private final int STRING_BASE = 16; 73 static private final int STRING_SIZE = 20; 74 static private final int FILE_SIZE = 24; 75 static private final int ELEMENTS = 28; 76 77 static private final int INT_SIZE = 4; 78 79 static private final int TABLE_ENTRY_SIZE = 2 * INT_SIZE; 80 81 private int capacity; // number of entries 82 private int table_base; // offset from start of file, in bytes 83 private int string_base; // offset from start of file, in bytes 84 private int string_size; // size of string table, in bytes 85 private int file_size; // size of file, in bytes; 86 private int elements; // number of elements in table 87 88 private long length; // the length of the underlying file 89 90 private final File name; // The name of the underlying file 91 92 static private final int UNUSED_ENTRY = -1; 93 94 static public final int KEYS = 0; 95 static public final int VALUES = 1; 96 static public final int ENTRIES = 2; 97 98 private HashMap values; // A map of strings in the string table. 99 100 FileChannel fc; // The underlying file channel. 101 102 static final public class AccessMode 103 { 104 private final FileChannel.MapMode mapMode; 105 106 static 107 { 108 READ_ONLY = new AccessMode(FileChannel.MapMode.READ_ONLY); 109 READ_WRITE = new AccessMode(FileChannel.MapMode.READ_WRITE); 110 PRIVATE = new AccessMode(FileChannel.MapMode.PRIVATE); 111 } 112 113 public static final AccessMode READ_ONLY; 114 public static final AccessMode READ_WRITE; 115 public static final AccessMode PRIVATE; 116 AccessMode(FileChannel.MapMode mode)117 private AccessMode(FileChannel.MapMode mode) 118 { 119 this.mapMode = mode; 120 } 121 } 122 PersistentByteMap(File name)123 private PersistentByteMap(File name) 124 { 125 this.name = name; 126 } 127 PersistentByteMap(String filename, AccessMode mode)128 public PersistentByteMap(String filename, AccessMode mode) 129 throws IOException 130 { 131 this(new File(filename), mode); 132 } 133 PersistentByteMap(File f, AccessMode mode)134 public PersistentByteMap(File f, AccessMode mode) 135 throws IOException 136 { 137 name = f; 138 139 if (mode == AccessMode.READ_ONLY) 140 { 141 FileInputStream fis = new FileInputStream(f); 142 fc = fis.getChannel(); 143 } 144 else 145 { 146 RandomAccessFile fos = new RandomAccessFile(f, "rw"); 147 fc = fos.getChannel(); 148 } 149 150 length = fc.size(); 151 buf = fc.map(mode.mapMode, 0, length); 152 153 int magic = getWord (MAGIC); 154 if (magic != 0x67636a64) /* "gcjd" */ 155 throw new IllegalArgumentException(f.getName()); 156 157 table_base = getWord (TABLE_BASE); 158 capacity = getWord (CAPACITY); 159 string_base = getWord (STRING_BASE); 160 string_size = getWord (STRING_SIZE); 161 file_size = getWord (FILE_SIZE); 162 elements = getWord (ELEMENTS); 163 164 // FIXME: Insert a bunch of sanity checks here 165 } 166 init(PersistentByteMap m, File f, int capacity, int strtabSize)167 private void init (PersistentByteMap m, File f, int capacity, int strtabSize) 168 throws IOException 169 { 170 f.createNewFile(); 171 RandomAccessFile raf = new RandomAccessFile(f, "rw"); 172 173 { 174 // The user has explicitly provided a size for the table. 175 // We're going to make that size prime. This isn't 176 // strictly necessary but it can't hurt. 177 // 178 // We expand the size by 3/2 and round the result because the 179 // hash table is intolerably slow when more than 2/3 full. 180 181 BigInteger size = new BigInteger(Integer.toString(((capacity*3)+1)/2)); 182 BigInteger two = BigInteger.ONE.add(BigInteger.ONE); 183 184 if (size.getLowestSetBit() != 0) // A hard way to say isEven() 185 size = size.add(BigInteger.ONE); 186 187 while (! size.isProbablePrime(10)) 188 size = size.add(two); 189 190 this.capacity = capacity = size.intValue(); 191 } 192 193 table_base = 64; 194 string_base = table_base + capacity * TABLE_ENTRY_SIZE; 195 string_size = 0; 196 file_size = string_base; 197 elements = 0; 198 199 int totalFileSize = string_base + strtabSize; 200 201 // Create the file; this rounds up the size of the file to a fixed 202 // number of 4k pages. 203 byte[] _4k = new byte[4096]; 204 for (long i = 0; i < totalFileSize; i+= 4096) 205 raf.write(_4k); 206 207 fc = raf.getChannel(); 208 buf = fc.map(FileChannel.MapMode.READ_WRITE, 0, raf.length()); 209 210 for (int i = 0; i < capacity; i++) 211 putKeyPos(UNUSED_ENTRY, i); 212 213 putWord(0x67636a64, MAGIC); 214 putWord(0x01, VERSION); 215 putWord(capacity, CAPACITY); 216 putWord(table_base, TABLE_BASE); 217 putWord(string_base, STRING_BASE); 218 putWord(file_size, FILE_SIZE); 219 putWord(elements, ELEMENTS); 220 buf.force(); 221 222 length = fc.size(); 223 string_size = 0; 224 } 225 226 static public PersistentByteMap emptyPersistentByteMap(File name, int capacity, int strtabSize)227 emptyPersistentByteMap(File name, int capacity, int strtabSize) 228 throws IOException 229 { 230 PersistentByteMap m = new PersistentByteMap(name); 231 m.init(m, name, capacity, strtabSize); 232 return m; 233 } 234 getWord(int index)235 private int getWord (int index) 236 { 237 buf.position(index); 238 byte[] wordBuf = new byte[4]; 239 buf.get(wordBuf); 240 241 int result = (int)wordBuf[0]&0xff; 242 result += ((int)wordBuf[1]&0xff) << 8; 243 result += ((int)wordBuf[2]&0xff) << 16; 244 result += ((int)wordBuf[3]&0xff) << 24; 245 return result; 246 } 247 putWord(int word, int index)248 private void putWord (int word, int index) 249 { 250 buf.position(index); 251 byte[] wordBuf = new byte[4]; 252 wordBuf[0] = (byte)(word); 253 wordBuf[1] = (byte)(word >>> 8); 254 wordBuf[2] = (byte)(word >>> 16); 255 wordBuf[3] = (byte)(word >>> 24); 256 buf.put(wordBuf); 257 } 258 entrySet()259 public Set entrySet() 260 { 261 return null; 262 } 263 getBucket(int n)264 private int getBucket(int n) 265 { 266 return table_base + (2*n * INT_SIZE); 267 } 268 getKeyPos(int n)269 private int getKeyPos(int n) 270 { 271 return getWord(getBucket(n)); 272 } 273 getValuePos(int n)274 private int getValuePos(int n) 275 { 276 return getWord(getBucket(n) + INT_SIZE); 277 } 278 putKeyPos(int index, int n)279 private void putKeyPos(int index, int n) 280 { 281 putWord(index, getBucket(n)); 282 } 283 putValuePos(int index, int n)284 private void putValuePos(int index, int n) 285 { 286 putWord(index, getBucket(n) + INT_SIZE); 287 } 288 getBytes(int n)289 private byte[] getBytes(int n) 290 { 291 int len = getWord (string_base + n); 292 int base = string_base + n + INT_SIZE; 293 byte[] key = new byte[len]; 294 buf.position(base); 295 buf.get(key, 0, len); 296 return key; 297 } 298 hash(byte[] b)299 private int hash (byte[] b) 300 { 301 // We assume that the message digest is evenly distributed, so we 302 // only need to use a few bytes of it as the hash function. 303 long hashIndex 304 = ((b[0]&0xffL) 305 + ((b[1]&0xffL)<<8) 306 + ((b[2]&0xffL)<<16) 307 + ((b[3]&0xffL)<<24)); 308 long result = hashIndex % (long)capacity; 309 return (int)result; 310 } 311 get(byte[] digest)312 public byte[] get(byte[] digest) 313 { 314 int hashIndex = hash(digest); 315 316 do 317 { 318 int k = getKeyPos(hashIndex); 319 if (k == UNUSED_ENTRY) 320 return null; 321 322 if (Arrays.equals ((byte[])digest, getBytes(k))) 323 return getBytes(getValuePos(hashIndex)); 324 325 // Use linear probing to resolve hash collisions. This may 326 // not be theoretically as good as open addressing, but it has 327 // good cache behviour. 328 hashIndex++; 329 hashIndex %= capacity; 330 } 331 while (true); 332 } 333 put(byte[] digest, byte[] value)334 public void put(byte[] digest, byte[] value) 335 throws IllegalAccessException 336 { 337 int hashIndex = hash(digest); 338 339 if (elements >= capacity()) 340 throw new IllegalAccessException("Table Full: " + elements); 341 342 do 343 { 344 int k = getKeyPos(hashIndex); 345 if (k == UNUSED_ENTRY) 346 { 347 int newKey = addBytes(digest); 348 putKeyPos(newKey, hashIndex); 349 int newValue = addBytes(value); 350 putValuePos(newValue, hashIndex); 351 elements++; 352 putWord(elements, ELEMENTS); 353 return; 354 } 355 else if (Arrays.equals (digest, getBytes(k))) 356 { 357 int newValue = addBytes((byte[])value); 358 putValuePos(newValue, hashIndex); 359 return; 360 } 361 362 hashIndex++; 363 hashIndex %= capacity; 364 } 365 while (true); 366 } 367 addBytes(byte[] data)368 private int addBytes (byte[] data) 369 throws IllegalAccessException 370 { 371 if (data.length > 16) 372 { 373 // Keep track of long strings in the hope that we will be able 374 // to re-use them. 375 if (values == null) 376 { 377 values = new HashMap(); 378 379 for (int i = 0; i < capacity; i++) 380 if (getKeyPos(i) != UNUSED_ENTRY) 381 { 382 int pos = getValuePos(i); 383 ByteWrapper bytes = new ByteWrapper(getBytes(pos)); 384 values.put(bytes, new Integer(pos)); 385 } 386 } 387 388 { 389 Object result = values.get(new ByteWrapper(data)); 390 if (result != null) 391 { 392 // We already have this value in the string table 393 return ((Integer)result).intValue(); 394 } 395 } 396 } 397 398 if (data.length + INT_SIZE >= this.length) 399 throw new IllegalAccessException("String table Full"); 400 401 int extent = string_base+string_size; 402 int top = extent; 403 putWord(data.length, extent); 404 extent += INT_SIZE; 405 buf.position(extent); 406 buf.put(data, 0, data.length); 407 extent += data.length; 408 extent += INT_SIZE-1; 409 extent &= ~(INT_SIZE-1); // align 410 string_size = extent - string_base; 411 file_size = extent; 412 putWord (string_size, STRING_SIZE); 413 putWord (file_size, FILE_SIZE); 414 415 if (data.length > 16) 416 values.put(new ByteWrapper(data), new Integer(top - string_base)); 417 418 return top - string_base; 419 } 420 iterator(int type)421 public Iterator iterator(int type) 422 { 423 return new HashIterator(type); 424 } 425 size()426 public int size() 427 { 428 return elements; 429 } 430 stringTableSize()431 public int stringTableSize() 432 { 433 return string_size; 434 } 435 capacity()436 public int capacity() 437 { 438 // With the the table 2/3 full there will be on average 2 probes 439 // for a successful search and 5 probes for an unsuccessful one. 440 return capacity * 2/3; 441 } 442 force()443 public void force() 444 { 445 buf.force(); 446 } 447 getFile()448 public File getFile() 449 { 450 return name; 451 } 452 453 // Close the map. Once this has been done, the map can no longer be 454 // used. close()455 public void close() throws IOException 456 { 457 force(); 458 fc.close(); 459 } 460 461 public void putAll(PersistentByteMap t)462 putAll(PersistentByteMap t) 463 throws IllegalAccessException 464 { 465 // We can use a fast copy if the size of a map has not changed. 466 if (this.elements == 0 && t.capacity == this.capacity 467 && t.length == this.length) 468 { 469 this.buf.position(0); 470 t.buf.position(0); 471 this.buf.put(t.buf); 472 this.table_base = t.table_base; 473 this.string_base = t.string_base; 474 this.string_size = t.string_size; 475 this.file_size = t.file_size; 476 this.elements = t.elements; 477 if (t.values != null) 478 this.values = (HashMap)t.values.clone(); 479 return; 480 } 481 482 // Otherwise do it the hard way. 483 Iterator iterator = t.iterator(PersistentByteMap.ENTRIES); 484 while (iterator.hasNext()) 485 { 486 PersistentByteMap.MapEntry entry 487 = (PersistentByteMap.MapEntry)iterator.next(); 488 this.put((byte[])entry.getKey(), (byte[])entry.getValue()); 489 } 490 } 491 492 493 private final class HashIterator implements Iterator 494 { 495 /** Current index in the physical hash table. */ 496 497 private int idx; 498 private int count; 499 private final int type; 500 501 /** 502 * Construct a new HashIterator with the supplied type. 503 * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} 504 */ HashIterator(int type)505 HashIterator(int type) 506 { 507 this.type = type; 508 count = elements; 509 idx = 0; 510 } 511 512 /** 513 * Returns true if the Iterator has more elements. 514 * @return true if there are more elements 515 * @throws ConcurrentModificationException if the HashMap was modified 516 */ hasNext()517 public boolean hasNext() 518 { 519 return count > 0; 520 } 521 522 /** 523 * Returns the next element in the Iterator's sequential view. 524 * @return the next element 525 * @throws ConcurrentModificationException if the HashMap was modified 526 * @throws NoSuchElementException if there is none 527 */ next()528 public Object next() 529 { 530 count--; 531 for (int i = idx; i < capacity; i++) 532 if (getKeyPos(i) != UNUSED_ENTRY) 533 { 534 idx = i+1; 535 if (type == VALUES) 536 return getBytes(getValuePos(i)); 537 if (type == KEYS) 538 return getBytes(getKeyPos(i)); 539 return new MapEntry(i, 540 getBytes(getKeyPos(i)), 541 getBytes(getValuePos(i))); 542 } 543 return null; 544 } 545 546 /** 547 * Remove from the underlying collection the last element returned 548 * by next (optional operation). This method can be called only 549 * once after each call to <code>next()</code>. It does not affect 550 * what will be returned by subsequent calls to next. 551 * 552 * @throws IllegalStateException if next has not yet been called 553 * or remove has already been called since the last call 554 * to next. 555 * @throws UnsupportedOperationException if this Iterator does not 556 * support the remove operation. 557 */ remove()558 public void remove() 559 { 560 throw new UnsupportedOperationException(); 561 } 562 } 563 564 static public final class MapEntry 565 { 566 private final Object key; 567 private final Object value; 568 private final int bucket; 569 MapEntry(int bucket, Object newKey, Object newValue)570 public MapEntry(int bucket, Object newKey, Object newValue) 571 { 572 this.key = newKey; 573 this.value = newValue; 574 this.bucket = bucket; 575 } 576 getKey()577 public final Object getKey() 578 { 579 return key; 580 } 581 getValue()582 public final Object getValue() 583 { 584 return value; 585 } 586 getBucket()587 public final int getBucket() 588 { 589 return bucket; 590 } 591 } 592 593 // A wrapper class for a byte array that allows collections to be 594 // made. 595 private final class ByteWrapper 596 { 597 final byte[] bytes; 598 final int hash; 599 ByteWrapper(byte[] bytes)600 public ByteWrapper (byte[] bytes) 601 { 602 int sum = 0; 603 this.bytes = bytes; 604 for (int i = 0; i < bytes.length; i++) 605 sum += bytes[i]; 606 hash = sum; 607 } 608 hashCode()609 public int hashCode() 610 { 611 return hash; 612 } 613 equals(Object obj)614 public boolean equals(Object obj) 615 { 616 return Arrays.equals(bytes, ((ByteWrapper)obj).bytes); 617 } 618 } 619 } 620