source: trunk/gsdl3/src/packages/javagdbm/java/au/com/pharos/gdbm/GdbmFile.java@ 10737

Last change on this file since 10737 was 10737, checked in by kjdon, 19 years ago

Java Wrapper for GDBM, from Martin Pool. Original website gone, so added it all in here. I have modified the Makefiles to work in greenstone, and on macs, and added windows makefiles

  • Property svn:keywords set to Author Date Id Revision
File size: 16.2 KB
Line 
1/*
2 * Copyright (C) 1997 Pharos IP Pty Ltd
3 * $Id: GdbmFile.java 10737 2005-10-19 03:06:40Z kjdon $
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20package au.com.pharos.gdbm;
21
22import java.util.Enumeration;
23import java.util.NoSuchElementException;
24
25import au.com.pharos.packing.Packing;
26import au.com.pharos.packing.RawPacking;
27
28/** Java interface to a GDBM database table.
29 *
30 * <P>This database is a simple on-disk hash table.
31 *
32 * <P>Both the hash keys and values
33 * are binary strings of any length. They are converted to and
34 * from Java objects using customizable packing strategies.
35 *
36 * <P>The implementation of this class consists of two levels: a
37 * Java-level interface, and a set of private native functions
38 * implemented in C. As much functionality as possible is implemented
39 * in Java: the C functions generally just marshal the information for
40 * presentation to the native GDBM library.
41 *
42 * <P>The native library <CODE>gdbmjava</CODE> must be available
43 * for dynamic loading in a system-dependant manner.
44 *
45 * <p>See the GDBM documentation file, and the
46 * <A HREF="http://www.pharos.com.au/gdbm/">JavaGDBM home page</A>
47 * for more information.
48 *
49 * @see au.com.pharos.packing.Packing
50 * @see au.com.pharos.gdbm.GdbmTest
51 * @see au.com.pharos.gdbm.GdbmDictionary
52 *
53 * @author Martin Pool
54 * @version $Revision: 10737 $
55 **/
56
57public class GdbmFile implements Closeable {
58 /** The GDBM handle for the database file, or 0 if the database has
59 * been closed. Java doesn't understand it, but it stores it and
60 * passes it to the C routines. */
61 private transient volatile long dbf;
62
63 // Remember the parameters used to open the database
64 private int openFlags;
65 private String dbFilename;
66
67 // -----------------------------------------------------------------
68 // Constructors
69
70 // Values must match those from gdbm.h !
71 /** Indicates that the caller will just read the database. Many
72 * readers may share a database. */
73 public final static int READER = 0;
74
75 /** The caller wants read/write access to an existing database and
76 * requires exclusive access. */
77 public final static int WRITER = 1;
78
79 /** The caller wants exclusive read/write access, and the database
80 * should be created if it does not already exist. */
81 public final static int WRCREAT = 2;
82
83 /** The caller wants exclusive read/write access, and the database
84 * should be replaced if it already exists. */
85 public final static int NEWDB = 3;
86
87 /** Flag indicating GDBM should write to the database without disc
88 * synchronization. This allows faster writes, but may produce an
89 * inconsistent database in the event of abnormal termination of
90 * the writer. */
91 public final static int FAST = 16;
92
93 // TODO: Allow FAST mode to be toggled
94
95 /** Creates a new GdbmFile object representing an disk database. The
96 * database is opened or created on disk.
97 *
98 * <P>Both key and value strategies default to
99 * RawPacking, meaning that the database will use raw byte arrays
100 * for both key and value.
101 *
102 * <P>TODO: Allow the caller to specify the block size and/or
103 * cache size.
104 *
105 * @param fileName the disk filename in which the data will be stored.
106 *
107 * @param flags any of READER, WRITER, WRCREAT, and NEWDB, optionally
108 * ORed with FAST
109 *
110 * @exception GdbmException if the database cannot be opened or
111 * created.
112 *
113 * @see GdbmFile#READER
114 * @see GdbmFile#WRITER
115 * @see GdbmFile#WRCREAT
116 * @see GdbmFile#NEWDB
117 * @see GdbmFile#FAST
118 */
119 public GdbmFile(String fileName, int flags)
120 throws GdbmException
121 {
122 openFlags = flags;
123 dbFilename = fileName;
124 keyPacking = new RawPacking();
125 valuePacking = new RawPacking();
126 dbf = gdbm_open(fileName, flags);
127 }
128
129 /** Close the database file if it is still open.
130 *
131 * <P><B>Note</B> that the disk file is locked against access from
132 * other processes while it is open. To prevent contention, it
133 * may be useful to explicitly close the file rather than waiting
134 * for the garbage-collector.
135 *
136 * @see GdbmFile#isOpen()
137 **/
138 public synchronized void close() throws GdbmException
139 {
140 if (dbf != 0)
141 gdbm_close(dbf);
142 dbf = 0; // No longer connected
143 }
144
145 /** Close the database when the GdbmFile is garbage-collected.
146 *
147 * @see GdbmFile#close()
148 */
149 public void finalize() throws GdbmException
150 {
151 close();
152 }
153
154 /* The Gdbm file is unlocked when the process terminates, but
155 * nevertheless it would be good to close it in finalization, in
156 * case the OS process continues after the JVM terminates.
157 *
158 * XXX: Unfortunately, anybody else can turn this off again: it
159 * would be nice if there was a way to avoid this. */
160 static {
161 System.runFinalizersOnExit(true);
162 }
163
164
165
166 // -----------------------------------------------------------------
167 // Packing strategies
168 private Packing keyPacking, valuePacking;
169
170 /** Set the object to be used as the packing strategy for
171 * converting key objects to and from the byte arrays stored in
172 * the database.
173 *
174 * <P>Depending on the class of Java object used as the key of your
175 * database, it may be appropriate to use a packing strategy from
176 * the
177 * <A HREF="Package-au.com.pharos.packing.html"><CODE>au.com.pharos.packing</CODE></A>
178 * package, or to define a new subclass of one of those strategies.
179 *
180 * @see au.com.pharos.packing.Packing
181 * @see GdbmFile#setValuePacking(au.com.pharos.packing.Packing)
182 **/
183 public void setKeyPacking(Packing newPacking) {
184 keyPacking = newPacking;
185 }
186
187 /** Set the object to be used as the packing strategy for
188 * converting value objects to and from the byte arrays stored
189 * in the database.
190 *
191 * <P>Depending on the class of Java object used as the key of your
192 * database, it may be appropriate to use a packing strategy from
193 * the
194 * <A HREF="Package-au.com.pharos.packing.html"><CODE>au.com.pharos.packing</CODE></A>
195 * package, or to define a new subclass of one of those strategies.
196 *
197 * @see au.com.pharos.packing.Packing
198 * @see GdbmFile#setKeyPacking(au.com.pharos.packing.Packing )
199 **/
200 public void setValuePacking(Packing newPacking) {
201 valuePacking = newPacking;
202 }
203
204 // -----------------------------------------------------------------
205
206 /** Returns a string indicating the version of the underlying GDBM
207 * library.
208 *
209 * @return the version of the native GDBM library.
210 **/
211 public static String getLibraryVersion()
212 {
213 return gdbm_getversion();
214 }
215
216 /** Return a string indicating the version of the JavaGDBM library
217 * wrapper.
218 *
219 * <P>The most current release is available from the
220 * <A HREF="http://www.pharos.com.au/javagdbm/">JavaGDBM home page</A>.
221 *
222 * @return the release number of the JavaGDBM library
223 **/
224 public static String getWrapperVersion()
225 {
226 return gdbm_wrapperVersion();
227 }
228
229 /** Indicate whether the database is writable.
230 *
231 * <P>Databases are opened in either read-write or read-only mode,
232 * and remain in that mode until they are closed.
233 *
234 * @return true if the database may be written; otherwise false.
235 *
236 * @see GdbmFile#GdbmFile(java.lang.String, int)
237 **/
238 public boolean isWritable()
239 {
240 return (openFlags & 0x03) != READER;
241 }
242
243 /** Indicate whether the database is open or not.
244 *
245 * <P>A database is open from the point of creation until close()
246 * is called, if ever, after which it is closed.
247 *
248 * @return false if the database has been closed; otherwise true.
249 *
250 * @see GdbmFile#close()
251 **/
252 public boolean isOpen()
253 {
254 return dbf != 0;
255 }
256
257
258 /** Compact the database file.
259 *
260 * <P>If you have had a lot of deletions and would like to shrink the
261 * space used by the GDBM file, this function will reorganize the
262 * database. GDBM will not shorten the length of a GDBM file
263 * (deleted file space will be reused) except by using this
264 * reorganization, though it will reuse the vacant space.
265 *
266 * <P>The database must be writable.
267 */
268 public void reorganize()
269 throws GdbmException
270 {
271 gdbm_reorganize(dbf);
272 }
273
274
275 /** Flush changes to disk.
276 *
277 * <P>This function is only required when the database is
278 * opened with the FAST flag set. By default, changes are
279 * flushed to disk after every update.
280 *
281 * <P>The database must be writable.
282 *
283 * @see GdbmFile#FAST
284 **/
285 public void sync()
286 throws GdbmException
287 {
288 gdbm_sync(dbf);
289 }
290
291
292
293 /** Retrieve the value corresponding to a particular key.
294 *
295 * @param key the key of the record to be retrieved
296 *
297 * @return the value of the record with the specified key; or
298 * null if the key is not present in the database.
299 */
300 public Object fetch(Object key) throws GdbmException
301 {
302 byte[] keyBytes = keyPacking.toBytes(key);
303 return valuePacking.fromBytes(gdbm_fetch(dbf, keyBytes));
304 }
305
306
307 /** Indicate whether the specified key is in the database,
308 * without returning the value.
309 *
310 * @param key the key of the record to be retrieved
311 *
312 * @return true if a record with the specified key is present;
313 * otherwise false.
314 */
315 public boolean exists(Object key) throws GdbmException
316 {
317 byte[] keyBytes = keyPacking.toBytes(key);
318 return gdbm_exists(dbf, keyBytes);
319 }
320
321
322
323 /** Store a value in the database, replacing any existing value
324 * with the same key.
325 *
326 * @param key key under which to store the value
327 *
328 * @param value value to be stored
329 *
330 * @exception GdbmException if the object is a reader; or
331 * if an IO error occurs
332 **/
333 public void store(Object key, Object value)
334 throws GdbmException
335 {
336 byte[] keyBytes = keyPacking.toBytes(key);
337 byte[] valueBytes = valuePacking.toBytes(value);
338 gdbm_store(dbf, keyBytes, valueBytes, true);
339 }
340
341
342
343 /** Store a value in the database, unless a record with the same
344 * key already exists.
345 *
346 * @param key key under which to store the value.
347 *
348 * @param value value to be stored.
349 *
350 * @exception GdbmException if a record with the specified key
351 * already exists; or if the object is a reader; or
352 * if an IO error occurs
353 */
354 public void storeNoReplace(Object key, Object value)
355 throws GdbmException
356 {
357 byte[] keyBytes = keyPacking.toBytes(key);
358 byte[] valueBytes = valuePacking.toBytes(value);
359 gdbm_store(dbf, keyBytes, valueBytes, false);
360 }
361
362
363
364 /** Remove a record from the database.
365 *
366 * @param key key of the record to be removed
367 *
368 * @exception GdbmException if there is no record with the
369 * specified key; or if the object is a reader; or if an IO error
370 * occurs.
371 **/
372 public void delete(Object key)
373 throws GdbmException
374 {
375 byte[] keyBytes = keyPacking.toBytes(key);
376 gdbm_delete(dbf, keyBytes);
377 }
378
379
380
381
382
383 // -----------------------------------------------------------------
384 // Iterator support
385
386 // TODO: Use a flag/timestamp approach to throw an exception if
387 // somebody modifies the database while they're trying to iterate
388 // over it? Approach suggested by Doug Lea's Collections API. --
389 // mbp
390
391 /** Return an enumeration which will return all of the keys for the
392 * database of the file in (apparently) random order.
393 *
394 * <P><B>Caution:</B> If the database is modified while
395 * an enumeration is in progress,
396 * then changes in the hash table may cause the enumeration to
397 * miss some records.
398 *
399 * @see java.util.Enumeration
400 **/
401 public Enumeration keys() throws GdbmException
402 {
403 // Return an inner class which will do the iteration in the
404 // context of this GdbmFile object.
405 return new KeyEnumeration();
406 }
407
408 // Synchronization is performed in the GdbmFile's methods
409 class KeyEnumeration implements Enumeration {
410 private byte[] currKey, nextKey;
411
412 KeyEnumeration() throws GdbmException {
413 currKey = null;
414 nextKey = getFirstKeyRaw();
415 }
416
417 public boolean hasMoreElements() {
418 return nextKey != null;
419 }
420
421 public Object nextElement() throws NoSuchElementException {
422 try {
423 currKey = nextKey;
424 if (currKey == null)
425 throw new NoSuchElementException();
426 else
427 nextKey = getNextKeyRaw(currKey);
428 return keyPacking.fromBytes(currKey);
429 } catch ( GdbmException e ) {
430 // Can't propagate this through java.util.Enumeration, dammit
431 throw new NoSuchElementException();
432 }
433 }
434 }
435
436 /** Start a visit to all keys in the hashtable, using raw data values.
437 *
438 * @return the first key in the hash table as a byte array; or null if
439 * the database is empty. */
440 byte[] getFirstKeyRaw() throws GdbmException
441 {
442 return gdbm_firstkey(dbf);
443 }
444
445 /** Return the first record in the hashtable.
446 *
447 * <P>Note that the database
448 * is not ordered, so the key returned is simply the first in the
449 * hashtable and effectively randomly selected.
450 *
451 * @return the first key in the hash table; or null if
452 * the database is empty.
453 *
454 * @see GdbmFile#getNextKey(java.lang.Object)
455 * @see GdbmFile#keys()
456 **/
457 Object getFirstKey() throws GdbmException
458 {
459 return keyPacking.fromBytes(getFirstKeyRaw());
460 }
461
462
463 /** Find and read the next key in the hashtable.
464 *
465 * @return the next key; or null if <EM>keyBytes</EM> is the last key
466 * in the hashtable.
467 **/
468 byte[] getNextKeyRaw(byte[] keyBytes) throws GdbmException
469 {
470 return gdbm_nextkey(dbf, keyBytes);
471 }
472
473
474 /** Check whether the database is empty.
475 *
476 * @return true if the database contains no records; otherwise
477 * false.
478 **/
479 public boolean isEmpty() throws GdbmException
480 {
481 return getFirstKeyRaw() == null;
482 }
483
484
485 /** Count the records in the database.
486 *
487 * <P>This is implemented by iterating over the database, and so is
488 * a fairly expensive operation.
489 *
490 * <P>This method locks the database to make sure the count is
491 * accurate.
492 *
493 * @return the number of records in the database
494 * @see GdbmFile#isEmpty()
495 **/
496 public synchronized int size() throws GdbmException
497 {
498 int s = 0;
499 byte[] key = getFirstKeyRaw();
500 while (key != null) {
501 s++;
502 key = getNextKeyRaw(key);
503 }
504 return s;
505 }
506
507
508 static {
509 String libraryFile = System.getProperty
510 ("au.com.pharos.gdbm.libraryFile");
511
512 if (libraryFile != null) {
513 System.load(libraryFile);
514 } else {
515 System.loadLibrary("gdbmjava");
516 }
517 }
518
519 private synchronized native long
520 gdbm_open(String fileName, int flags)
521 throws GdbmException;
522
523 private synchronized native void
524 gdbm_close(long dbf);
525
526 private synchronized native void
527 gdbm_store(long dbf,
528 byte[] key,
529 byte[] content,
530 boolean replace);
531
532 private synchronized native byte[]
533 gdbm_fetch(long dbf,
534 byte[] key);
535
536 private synchronized native boolean
537 gdbm_exists(long dbf,
538 byte[] key);
539
540 private synchronized native void
541 gdbm_delete(long dbf,
542 byte[] key);
543
544 private synchronized native byte[]
545 gdbm_firstkey(long dbf)
546 throws GdbmException;
547
548 private synchronized native byte[]
549 gdbm_nextkey(long dbf, byte[] key)
550 throws GdbmException;
551
552 private synchronized static native String
553 gdbm_getversion();
554
555 private synchronized static native String
556 gdbm_wrapperVersion();
557
558 private synchronized native void
559 gdbm_reorganize(long dbf);
560
561 private synchronized native void
562 gdbm_sync(long dbf);
563}
Note: See TracBrowser for help on using the repository browser.