source: gs2-extensions/tdb/trunk/src/jni-0.1/org_greenstone_tdbjava_TDBJava.c@ 30275

Last change on this file since 30275 was 30275, checked in by jmt12, 9 years ago

Minor change to the way fcntl_flags are set so that they look exactly the same as the working code in tdbcli et al. Shouldn't have any effect, but was chasing down strange error (eventually found to be caused by Java's static library loader and a tdb error condition)

File size: 15.2 KB
Line 
1/*
2 * Copyright (C) 2015 Greenstone Digital Libraries, University of Waikato
3 * $Id$
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
20#include <errno.h>
21#include <fcntl.h>
22#include <limits.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include <jni.h>
28/** JNI Primitive Data-types to keep in mind:
29 * jbyte => signed 8 bits
30 * jchar => unsigned 16 bits
31 * jint => signed 32 bits
32 * jlong => signed 64 bits
33 * jsize => jint
34 */
35
36#include "tdb.h"
37/** TDB Data-structures to keep in mind:
38 *
39 * typedef struct TDB_DATA {
40 * unsigned char *dptr;
41 * size_t dsize;
42 * } TDB_DATA;
43 *
44 * where:
45 * size_t => unsigned 32 bits
46 */
47
48/* Cookie for you if you can figure out from the above two comments where I
49 * will spend a nightmarish amount of time trying to get the code to work. */
50
51#include "org_greenstone_tdbjava_TDBJava.h"
52
53#ifdef DEBUG
54#define ASSERT(x) if (!(x)) { \
55 fprintf(stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
56 abort(); }
57#else /* !DEBUG */
58#define ASSERT(x)
59#endif /* !DEBUG */
60
61/* The Java class within which the native methods are declared */
62#define JAVA_CLASS "org.greenstone.tdbjava.TDBJava"
63
64/** While tdb.h uses a non-pointer TDB_CONTEXT, I'll add this to make the
65 * code transported from javaGDBM more compatible. */
66typedef struct tdb_context *TDB_FILE;
67
68/** These defined functions come straight from javagdbm. */
69#define TDB_EXCEPTION(env) tdbException(env, __FILE__, __LINE__)
70#define NULL_PTR_EXCEPTION(env) nullPtrException(env, __FILE__, __LINE__)
71#define CHECK_NOT_NULL(ptr, env) if (!ptr) { NULL_PTR_EXCEPTION(env); return 0; }
72#define CHECK_NOT_NULL_VOID(ptr, env) if (!ptr) { NULL_PTR_EXCEPTION(env); return; }
73
74/* Convert between a jlong and a void ptr using a well-defined cast.
75 * (Casting between a pointer and an integer of different sizes spooks
76 * both gcc and mbp. */
77#if (SIZEOF_VOID_P == SIZEOF_LONG)
78# define DBF_TO_JLONG(x) ((jlong)((long) x))
79# define JLONG_TO_DBF(x) ((TDB_CONTEXT *)((long) x))
80#elif (SIZEOF_VOID_P == SIZEOF_INT)
81# define DBF_TO_JLONG(x) ((jlong)((int) (x)))
82# define JLONG_TO_DBF(x) ((TDB_CONTEXT *)((int) (x)))
83#else
84# define DBF_TO_JLONG(x) ((jlong)(x))
85# define JLONG_TO_DBF(x) ((TDB_CONTEXT *)(x))
86#endif
87
88
89/** ===== Prototypes ===== **/
90/* Can't go in header as it is autogenerated. */
91void tdbException(JNIEnv *env, const char *file, int line);
92void nullPtrException(JNIEnv *env, const char *file, int line);
93void releaseArray(JNIEnv *env, jbyteArray array, TDB_DATA *fromDatum);
94void releaseArrayAbort(JNIEnv *env, jbyteArray array, TDB_DATA *fromDatum);
95int arrayToDatum(JNIEnv *env, jbyteArray fromArray, TDB_DATA *toDatum);
96jbyteArray datumToArray(JNIEnv *env, TDB_DATA *fromDatum);
97
98/** Define a successful TDB action to be one whose error code is TDB_SUCCESS or
99 * TDB_ERR_NOEXIST (for actions with the potential to try and access records
100 * that aren't there, such as tdb_exist() and tdb_store()).
101 */
102bool tdbCheckSuccess(int ecode)
103{
104 /* We are successful but only for certain definitions of success :P */
105 return (TDB_SUCCESS == ecode || TDB_ERR_NOEXIST == ecode);
106}
107/** tdbCheckSuccess(int) **/
108
109
110/*
111 * Class: org_greenstone_tdbjava_TDBJava
112 * Method: tdbGetVersion
113 * Signature: ()Ljava/lang/String;
114 */
115JNIEXPORT jstring JNICALL
116Java_org_greenstone_tdbjava_TDBJava_tdbGetVersion(JNIEnv *env,
117 jclass cls)
118{
119 return (*env)->NewStringUTF(env, "TDB 1.3.7");
120}
121/** tdbGetVersion(JNIEnv *, jclass) => jstring **/
122
123
124/*
125 * Class: org_greenstone_tdbjava_TDBJava
126 * Method: tdbWrapperVersion
127 * Signature: ()Ljava/lang/String;
128 */
129JNIEXPORT jstring JNICALL
130Java_org_greenstone_tdbjava_TDBJava_tdbWrapperVersion(JNIEnv *env,
131 jclass cls)
132{
133 return (*env)->NewStringUTF(env, "TDBJava built " __DATE__);
134}
135/** tdbWrapperVersion(JNIEnv *, jclass) => jstring **/
136
137
138/*
139 * Class: org_greenstone_tdbjava_TDBJava
140 * Method: tdbOpen
141 * Signature: (Ljava/lang/String;II)J
142 */
143JNIEXPORT jlong JNICALL
144Java_org_greenstone_tdbjava_TDBJava_tdbOpen(JNIEnv *env,
145 jobject obj,
146 jstring file_name,
147 jint tdb_flags,
148 jint open_flags)
149{
150 TDB_CONTEXT *dbf;
151 const char *utf_file_name;
152 int fcntl_flags = O_RDWR | O_CREAT;
153 utf_file_name = (*env)->GetStringUTFChars(env, file_name, 0);
154 if (!utf_file_name) {
155 return 0;
156 }
157 if (open_flags == 1) {
158 fcntl_flags = O_RDONLY;
159 }
160 setbuf(stderr, 0);
161 dbf = tdb_open((char *) utf_file_name, 0, tdb_flags, fcntl_flags, 0664);
162 (*env)->ReleaseStringUTFChars(env, file_name, utf_file_name);
163 // notably, the follow exception is the only time TDB_ERR_NOEXIST actually
164 // matters
165 if (!dbf) {
166 TDB_EXCEPTION(env);
167 return 0;
168 }
169 return DBF_TO_JLONG(dbf);
170}
171/** tdbOpen(JNIEnv *, jobject, jstring, jint, jint) => jlong **/
172
173
174/*
175 * Class: org_greenstone_tdbjava_TDBJava
176 * Method: tdbClose
177 * Signature: (J)V
178 */
179JNIEXPORT void JNICALL
180Java_org_greenstone_tdbjava_TDBJava_tdbClose(JNIEnv *env,
181 jobject obj,
182 jlong dbf)
183{
184 int ecode;
185 CHECK_NOT_NULL_VOID(dbf, env);
186 ecode = tdb_close(JLONG_TO_DBF(dbf));
187 if (TDB_SUCCESS != ecode) {
188 TDB_EXCEPTION(env);
189 }
190}
191/** tdbClose(JNIEnv *, jobject, jlong) => void **/
192
193
194/*
195 * Class: org_greenstone_tdbjava_TDBJava
196 * Method: tdbStore
197 * Signature: (J[B[BZ)V
198 */
199JNIEXPORT void JNICALL
200Java_org_greenstone_tdbjava_TDBJava_tdbStore(JNIEnv *env,
201 jobject obj,
202 jlong dbf,
203 jbyteArray key_array,
204 jbyteArray value_array,
205 jboolean replace)
206{
207 TDB_DATA key_datum;
208 TDB_DATA value_datum;
209 int ecode;
210 CHECK_NOT_NULL_VOID(dbf, env);
211 if (!arrayToDatum(env, key_array, &key_datum)) {
212 NULL_PTR_EXCEPTION(env);
213 return;
214 }
215 if (!arrayToDatum(env, value_array, &value_datum)) {
216 NULL_PTR_EXCEPTION(env);
217 return;
218 }
219 ecode = tdb_store(JLONG_TO_DBF(dbf), key_datum, value_datum, TDB_REPLACE);
220 releaseArrayAbort(env, key_array, &key_datum);
221 releaseArrayAbort(env, value_array, &value_datum);
222 if (!tdbCheckSuccess(ecode)) {
223 TDB_EXCEPTION(env);
224 }
225}
226/** tdbStore(JNIEnv *, jobject, jlong, jbyteArray, jbyteArray, jboolean) => void **/
227
228
229/*
230 * Class: org_greenstone_tdbjava_TDBJava
231 * Method: tdbFetch
232 * Signature: (J[B)[B
233 */
234JNIEXPORT jbyteArray JNICALL
235Java_org_greenstone_tdbjava_TDBJava_tdbFetch(JNIEnv *env,
236 jobject this,
237 jlong dbf,
238 jbyteArray key_array)
239{
240 TDB_DATA key_datum;
241 TDB_DATA value_datum;
242 jbyteArray value_array;
243 int ecode;
244 CHECK_NOT_NULL(dbf, env);
245 if (!arrayToDatum(env, key_array, &key_datum)) {
246 NULL_PTR_EXCEPTION(env);
247 return 0;
248 }
249 value_datum = tdb_fetch(JLONG_TO_DBF(dbf), key_datum);
250 releaseArrayAbort(env, key_array, &key_datum);
251 ecode = tdb_error(JLONG_TO_DBF(dbf));
252 if (!tdbCheckSuccess(ecode)) {
253 TDB_EXCEPTION(env);
254 return 0;
255 }
256 // no such key case
257 if (!value_datum.dptr) {
258 return 0;
259 }
260 value_array = datumToArray(env, &value_datum);
261 free(value_datum.dptr);
262 return value_array;
263}
264/** tdbFetch(JNIEnv *, jobject, jlong, jbyteArray) => jbyteArray **/
265
266
267/*
268 * Class: org_greenstone_tdbjava_TDBJava
269 * Method: tdbExists
270 * Signature: (J[B)Z
271 */
272JNIEXPORT jboolean JNICALL
273Java_org_greenstone_tdbjava_TDBJava_tdbExists(JNIEnv *env,
274 jobject obj,
275 jlong dbf,
276 jbyteArray key_array)
277{
278 TDB_DATA key_datum;
279 int ecode, result;
280 CHECK_NOT_NULL(dbf, env);
281 if (!arrayToDatum(env, key_array, &key_datum)) {
282 NULL_PTR_EXCEPTION(env);
283 return JNI_FALSE;
284 }
285 result = tdb_exists(JLONG_TO_DBF(dbf), key_datum);
286 // Gah - tdb_exists can set TDB_ERR_NOEXIST even if it works properly
287 ecode = tdb_error(JLONG_TO_DBF(dbf));
288 if (!tdbCheckSuccess(ecode)) {
289 TDB_EXCEPTION(env);
290 return 0;
291 }
292 releaseArrayAbort(env, key_array, &key_datum);
293 return result ? JNI_TRUE : JNI_FALSE;
294}
295/** tdbExists(JNIEnv *, jobject, jlong, jbyteArray) => jboolean **/
296
297
298/*
299 * Class: org_greenstone_tdbjava_TDBJava
300 * Method: tdbDelete
301 * Signature: (J[B)V
302 */
303JNIEXPORT void JNICALL
304Java_org_greenstone_tdbjava_TDBJava_tdbDelete(JNIEnv *env,
305 jobject obj,
306 jlong dbf,
307 jbyteArray key_array)
308{
309 TDB_DATA key_datum;
310 int ecode;
311 CHECK_NOT_NULL_VOID(dbf, env);
312 if (!arrayToDatum(env, key_array, &key_datum)) {
313 NULL_PTR_EXCEPTION(env);
314 return;
315 }
316 tdb_delete(JLONG_TO_DBF(dbf), key_datum);
317 releaseArrayAbort(env, key_array, &key_datum);
318 // just to be different, while tdb_delete does return a result value it
319 // doesn't reflect any error state (just whether a key/value was found and
320 // deleted or not) leaving us to once again get tdb_error()
321 ecode = tdb_error(JLONG_TO_DBF(dbf));
322 if (!tdbCheckSuccess(ecode)) {
323 TDB_EXCEPTION(env);
324 }
325}
326/** tdbDelete(JNIEnv *, jobject, jlong, jbyteArray) => void **/
327
328/*
329 * Class: org_greenstone_tdbjava_TDBJava
330 * Method: tdbFirstKey
331 * Signature: (J)[B
332 */
333JNIEXPORT jbyteArray JNICALL
334Java_org_greenstone_tdbjava_TDBJava_tdbFirstKey(JNIEnv *env,
335 jobject obj,
336 jlong dbf)
337{
338 TDB_DATA key_datum;
339 jbyteArray key_array;
340 int ecode;
341 CHECK_NOT_NULL(dbf, env);
342 key_datum = tdb_firstkey(JLONG_TO_DBF(dbf));
343 ecode = tdb_error(JLONG_TO_DBF(dbf));
344 if (!tdbCheckSuccess(ecode)) {
345 TDB_EXCEPTION(env);
346 return 0;
347 }
348 // no such key case
349 if (!key_datum.dptr) {
350 return 0;
351 }
352 key_array = datumToArray(env, &key_datum);
353 free(key_datum.dptr);
354 return key_array;
355}
356/** tdbFirstKey(JNIEnv *, jobject, jlong) => jbyteArray **/
357
358
359/*
360 * Class: org_greenstone_tdbjava_TDBJava
361 * Method: tdbNextKey
362 * Signature: (J[B)[B
363 */
364JNIEXPORT jbyteArray JNICALL
365Java_org_greenstone_tdbjava_TDBJava_tdbNextKey(JNIEnv *env,
366 jobject this,
367 jlong dbf,
368 jbyteArray key_array)
369{
370 TDB_DATA key_datum;
371 TDB_DATA next_datum;
372 jbyteArray next_array;
373 int ecode;
374 CHECK_NOT_NULL(dbf, env);
375 if (!arrayToDatum(env, key_array, &key_datum)) {
376 NULL_PTR_EXCEPTION(env);
377 return 0;
378 }
379 next_datum = tdb_nextkey(JLONG_TO_DBF(dbf), key_datum);
380 releaseArrayAbort(env, key_array, &key_datum);
381 ecode = tdb_error(JLONG_TO_DBF(dbf));
382 if (!tdbCheckSuccess(ecode)) {
383 TDB_EXCEPTION(env);
384 return 0;
385 }
386 // no such key case
387 if (!next_datum.dptr) {
388 return 0;
389 }
390 next_array = datumToArray(env, &next_datum);
391 free(next_datum.dptr);
392 return next_array;
393}
394/** tdbNextKey(JNIEnv *, jobject, jlong, jbyteArray) => jbyteArray **/
395
396
397/******************************************************************************
398 * Following are support functions which aid in interfacing C to Java.
399 ******************************************************************************/
400
401/** Create a new Java byte array from a TDB_Data, and return a
402 * pointer thereto. */
403jbyteArray datumToArray(JNIEnv *env,
404 TDB_DATA *from_datum)
405{
406 jbyte *jbyte_dptr;
407 jbyteArray to_array = 0;
408 jsize length;
409 if (from_datum->dsize <= 0 || !from_datum->dptr) {
410 TDB_EXCEPTION(env);
411 return 0;
412 }
413 to_array = (*env)->NewByteArray(env, from_datum->dsize);
414 ASSERT(to_array);
415 // We won't be able to meaningfully cast an unsigned int data length larger
416 // than the limit of signed integers due to JNI limitations. I bite my thumb
417 // at thee, signedness
418 if (from_datum->dsize > INT_MAX) {
419 TDB_EXCEPTION(env);
420 return 0;
421 }
422 length = (signed int) from_datum->dsize;
423 // the following dptr cast *should* be safe as the data arrived in the dptr
424 // from a jbyteArray anyway
425 jbyte_dptr = (jbyte*) from_datum->dptr;
426 (*env)->SetByteArrayRegion(env, to_array, 0, length, jbyte_dptr);
427 return to_array;
428}
429/** datumToArray(JNIEnv *, TDB_DATA) **/
430
431
432/** Convert a Java byte array to a TDB_DATA object.
433 *
434 * The Java array is pinned or copied for use in the datum, and must
435 * be released after use by releaseBytes.
436 *
437 * Returns true if the array is non-null and could be pinned. Otherwise,
438 * returns false.
439 */
440int arrayToDatum(JNIEnv *env,
441 jbyteArray from_array,
442 TDB_DATA *to_datum)
443{
444 int result = 0;
445 jbyte *jbyte_dptr;
446 if (from_array) {
447 jbyte_dptr = (*env)->GetByteArrayElements(env, from_array, 0);
448 // This cast should always be safe as the jbytes are 0-127 (never negative)
449 // and the dptr supports values in the range 0-255.
450 to_datum->dptr = (unsigned char*) jbyte_dptr;
451 jsize data_length = (*env)->GetArrayLength(env, from_array);
452 // Excluding the unlikely case of negative lengths, the dsize cast should
453 // be okay.
454 if (data_length > 0) {
455 to_datum->dsize = (unsigned int) data_length;
456 result = 1;
457 }
458 }
459 return result;
460}
461/** arrayToDatum(JNIEnv *, jbyteArray, TDB_DATA *) **/
462
463
464/** Release a byte array pinned or copied for use in a datum. */
465void releaseArray(JNIEnv *env,
466 jbyteArray array,
467 TDB_DATA *from_datum)
468{
469 jbyte *jbyte_dptr;
470 ASSERT(from_datum->dptr);
471 // @see datumToArray() for cast justification
472 jbyte_dptr = (jbyte *) from_datum->dptr;
473 (*env)->ReleaseByteArrayElements(env, array, jbyte_dptr, 0);
474 from_datum->dptr = 0; /* no longer valid */
475}
476/** releaseArray(JNIEnv *, jbyteArray, TDB_DATA) **/
477
478
479/** Release a byte array pinned or copied for use in a datum, aborting
480 * any changes. This potentially saves the runtime from having to
481 * copy back an unchanged array. */
482void releaseArrayAbort(JNIEnv *env,
483 jbyteArray array,
484 TDB_DATA *from_datum)
485{
486 jbyte *jbyte_dptr;
487 ASSERT(from_datum->dptr);
488 // @see datumToArray() for cast justification
489 jbyte_dptr = (jbyte *) from_datum->dptr;
490 (*env)->ReleaseByteArrayElements(env, array, jbyte_dptr, JNI_ABORT);
491 from_datum->dptr = 0; /* no longer valid */
492}
493/** releaseArrayAbort(JNIEnv *, jbyteArray, TDB_DATA) **/
494
495
496/** Throw a null pointer exception.
497 */
498void nullPtrException(JNIEnv *env,
499 const char *file,
500 int line)
501{
502 jclass exception_class;
503 char reason[1024];
504 sprintf(reason, "Null pointer exception in TDBJava (%s:%d)", file, line);
505 exception_class = (*env)->FindClass(env, "java/lang/NullPointerException");
506 ASSERT(exClass);
507 (*env)->ThrowNew(env, exception_class, reason);
508}
509/** nullPtrException(JNIEnv *, const char *, int) **/
510
511
512/** Translate the TDB error into a Java exception and throw same.
513 */
514void tdbException(JNIEnv *env,
515 const char *file,
516 int line)
517{
518 jclass exception_class;
519 static char reason[1500];
520 static char src_location[500];
521 exception_class = (*env)->FindClass(env, "org/greenstone/tdbjava/TDBJavaException");
522 ASSERT(exception_class);
523 strncpy(reason, strerror(errno), 500);
524 sprintf(src_location, " (%s:%d)", file, line);
525 strncat(reason, src_location, 495);
526 errno = TDB_SUCCESS; /* this one has been handled */
527 (*env)->ThrowNew(env, exception_class, reason);
528}
529/** tdbException(JNIEnv *, const char *, int) **/
Note: See TracBrowser for help on using the repository browser.