source: main/trunk/greenstone3/src/packages/javagdbm/java/au/com/pharos/gdbm/GdbmFile.java@ 30401

Last change on this file since 30401 was 30401, checked in by davidb, 8 years ago

Fix for MacOS 10.11, El Capitan

  • Property svn:keywords set to Author Date Id Revision
File size: 17.0 KB
Line 
1/*
2 * Copyright (C) 1997 Pharos IP Pty Ltd
3 * $Id: GdbmFile.java 30401 2016-03-13 21:33:13Z davidb $
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: 30401 $
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
516 String gsdlos = System.getenv("GSDLOS");
517 if (gsdlos!=null && gsdlos.equals("darwin")) {
518 // As of MacOX 10.11 (El Capitan), effectivly supresses DYLD_LIBRARY_PATH (does
519 // not propagate it to child processes). This is a result of changes to their
520 // security model, and seems to come into effect for 'untrusted' executables.
521 // Greenstone run as a regular user, is 'unstrusted'. It is possible, with
522 // admin rights, to override this, however that is not really a viable solution
523 // for our project. Hence the change here to use Systen.load() with an
524 // absolute pathname, rather than rely of System.loadLibrary().
525
526 String gsdl3srchome = System.getenv("GSDL3SRCHOME");
527 String full_jni_library = gsdl3srchome + "/lib/jni/libgdbmjava.jnilib";
528 System.load(full_jni_library);
529 }
530 else {
531
532 System.loadLibrary("gdbmjava");
533 }
534 }
535 }
536
537 private synchronized native long
538 gdbm_open(String fileName, int flags)
539 throws GdbmException;
540
541 private synchronized native void
542 gdbm_close(long dbf);
543
544 private synchronized native void
545 gdbm_store(long dbf,
546 byte[] key,
547 byte[] content,
548 boolean replace);
549
550 private synchronized native byte[]
551 gdbm_fetch(long dbf,
552 byte[] key);
553
554 private synchronized native boolean
555 gdbm_exists(long dbf,
556 byte[] key);
557
558 private synchronized native void
559 gdbm_delete(long dbf,
560 byte[] key);
561
562 private synchronized native byte[]
563 gdbm_firstkey(long dbf)
564 throws GdbmException;
565
566 private synchronized native byte[]
567 gdbm_nextkey(long dbf, byte[] key)
568 throws GdbmException;
569
570 private synchronized static native String
571 gdbm_getversion();
572
573 private synchronized static native String
574 gdbm_wrapperVersion();
575
576 private synchronized native void
577 gdbm_reorganize(long dbf);
578
579 private synchronized native void
580 gdbm_sync(long dbf);
581}
Note: See TracBrowser for help on using the repository browser.