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

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

Initial checkin of Java->JNI->TDB wrapper

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_CREAT | O_RDWR);
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.