1 /**
2 * This is a simple mechanism to bind Inkscape to Java, and thence
3 * to all of the nice things that can be layered upon that.
4 *
5 * Authors:
6 * Bob Jamison
7 *
8 * Copyright (C) 2007-2008 Bob Jamison
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 3 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 */
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <string.h>
33 #include <jni.h>
35 #include <sys/types.h>
36 #include <dirent.h>
39 #ifdef __WIN32__
40 #include <windows.h>
41 #else
42 #include <dlfcn.h>
43 #include <errno.h>
44 #endif
46 #include "javabind.h"
47 #include "javabind-private.h"
48 #include <path-prefix.h>
49 #include <prefix.h>
50 #include <glib/gmessages.h>
56 namespace Inkscape
57 {
59 namespace Bind
60 {
63 //########################################################################
64 //# DEFINITIONS
65 //########################################################################
67 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
71 //########################################################################
72 //# UTILITY
73 //########################################################################
75 jint getInt(JNIEnv *env, jobject obj, const char *name)
76 {
77 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
78 return env->GetIntField(obj, fid);
79 }
81 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
82 {
83 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
84 env->SetIntField(obj, fid, val);
85 }
87 jlong getLong(JNIEnv *env, jobject obj, const char *name)
88 {
89 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
90 return env->GetLongField(obj, fid);
91 }
93 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
94 {
95 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
96 env->SetLongField(obj, fid, val);
97 }
99 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
100 {
101 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
102 return env->GetFloatField(obj, fid);
103 }
105 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
106 {
107 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
108 env->SetFloatField(obj, fid, val);
109 }
111 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
112 {
113 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
114 return env->GetDoubleField(obj, fid);
115 }
117 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
118 {
119 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
120 env->SetDoubleField(obj, fid, val);
121 }
123 String getString(JNIEnv *env, jobject obj, const char *name)
124 {
125 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
126 jstring jstr = (jstring)env->GetObjectField(obj, fid);
127 const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
128 String str = chars;
129 env->ReleaseStringUTFChars(jstr, chars);
130 return str;
131 }
133 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
134 {
135 jstring jstr = env->NewStringUTF(val.c_str());
136 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
137 env->SetObjectField(obj, fid, jstr);
138 }
143 //########################################################################
144 //# CONSTRUCTOR/DESTRUCTOR
145 //########################################################################
147 static JavaBinderyImpl *_instance = NULL;
149 JavaBindery *JavaBindery::getInstance()
150 {
151 return JavaBinderyImpl::getInstance();
152 }
154 JavaBinderyImpl *JavaBinderyImpl::getInstance()
155 {
156 if (!_instance)
157 {
158 _instance = new JavaBinderyImpl();
159 }
160 return _instance;
161 }
163 JavaBinderyImpl::JavaBinderyImpl()
164 {
165 jvm = NULL;
166 env = NULL;
167 }
169 JavaBinderyImpl::~JavaBinderyImpl()
170 {
171 }
173 void err(const char *fmt, ...)
174 {
175 #if 0
176 va_list args;
177 fprintf(stderr, "JavaBinderyImpl err:");
178 va_start(args, fmt);
179 vfprintf(stderr, fmt, args);
180 va_end(args);
181 fprintf(stderr, "\n");
182 #else
183 va_list args;
184 g_warning("JavaBinderyImpl err:");
185 va_start(args, fmt);
186 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
187 va_end(args);
188 g_warning("\n");
189 #endif
190 }
192 void msg(const char *fmt, ...)
193 {
194 #if 0
195 va_list args;
196 fprintf(stdout, "JavaBinderyImpl:");
197 va_start(args, fmt);
198 vfprintf(stdout, fmt, args);
199 va_end(args);
200 fprintf(stdout, "\n");
201 #else
202 va_list args;
203 g_message("JavaBinderyImpl:");
204 va_start(args, fmt);
205 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
206 va_end(args);
207 g_message("\n");
208 #endif
209 }
211 bool JavaBinderyImpl::isLoaded()
212 {
213 return (jvm != (void *)0);
214 }
218 #ifdef __WIN32__
221 //########################################################################
222 //# W I N 3 2 S T Y L E
223 //########################################################################
226 #define DIR_SEPARATOR "\\"
227 #define PATH_SEPARATOR ";"
231 static bool getRegistryString(HKEY root, const char *keyName,
232 const char *valName, char *buf, int buflen)
233 {
234 HKEY key;
235 DWORD bufsiz = buflen;
236 RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
237 int ret = RegQueryValueEx(key, TEXT(valName),
238 NULL, NULL, (BYTE *)buf, &bufsiz);
239 if (ret != ERROR_SUCCESS)
240 {
241 err("Key '%s\\%s not found\n", keyName, valName);
242 return false;
243 }
244 RegCloseKey(key);
245 return true;
246 }
249 static CreateVMFunc getCreateVMFunc()
250 {
251 char verbuf[16];
252 char regpath[80];
253 strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
254 bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
255 regpath, "CurrentVersion", verbuf, 15);
256 if (!ret)
257 {
258 err("JVM CurrentVersion not found in registry\n");
259 return NULL;
260 }
261 strcat(regpath, "\\");
262 strcat(regpath, verbuf);
263 //msg("reg path: %s\n", regpath);
264 char libname[80];
265 ret = getRegistryString(HKEY_LOCAL_MACHINE,
266 regpath, "RuntimeLib", libname, 79);
267 if (!ret)
268 {
269 err("Current JVM RuntimeLib not found in registry\n");
270 return NULL;
271 }
272 //msg("jvm path: %s\n", libname);
273 HMODULE lib = LoadLibrary(libname);
274 if (!lib)
275 {
276 err("Java VM not found at '%s'", libname);
277 return NULL;
278 }
279 CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
280 if (!createVM)
281 {
282 err("Could not find 'JNI_CreateJavaVM' in shared library");
283 return NULL;
284 }
285 return createVM;
286 }
288 static void getJavaRoot(String &javaroot)
289 {
290 char exeName[80];
291 GetModuleFileName(NULL, exeName, 80);
292 char *slashPos = strrchr(exeName, '\\');
293 if (slashPos)
294 *slashPos = '\0';
295 javaroot = exeName;
296 javaroot.append("\\");
297 javaroot.append(INKSCAPE_JAVADIR);
298 }
301 #else
304 //########################################################################
305 //# U N I X S T Y L E
306 //########################################################################
309 #define DIR_SEPARATOR "/"
310 #define PATH_SEPARATOR ":"
313 /**
314 * Recursively descend into a directory looking for libjvm.so
315 */
316 static bool findJVMRecursive(const String &dirpath,
317 std::vector<String> &results)
318 {
319 DIR *dir = opendir(dirpath.c_str());
320 if (!dir)
321 return false;
322 bool ret = false;
323 while (true)
324 {
325 struct dirent *de = readdir(dir);
326 if (!de)
327 break;
328 String fname = de->d_name;
329 if (fname == "." || fname == "..")
330 continue;
331 String path = dirpath;
332 path.push_back('/');
333 path.append(fname);
334 if (fname == "libjvm.so")
335 {
336 ret = true;
337 results.push_back(path);
338 continue;
339 }
340 struct stat finfo;
341 if (lstat(path.c_str(), &finfo)<0)
342 {
343 break;
344 }
345 if (finfo.st_mode & S_IFDIR)
346 {
347 ret |= findJVMRecursive(path, results);
348 }
349 }
350 closedir(dir);
351 return ret;
352 }
355 static const char *commonJavaPaths[] =
356 {
357 "/usr/java",
358 "/usr/local/java",
359 "/usr/lib/jvm",
360 "/usr/local/lib/jvm",
361 NULL
362 };
364 /**
365 * Look for a Java VM (libjvm.so) in several Unix places
366 */
367 static bool findJVM(String &result)
368 {
369 std::vector<String> results;
370 int found = false;
372 /* Is there one specified by the user? */
373 const char *javaHome = getenv("JAVA_HOME");
374 if (javaHome && findJVMRecursive(javaHome, results))
375 found = true;
376 else for (const char **path = commonJavaPaths ; *path ; path++)
377 {
378 if (findJVMRecursive(*path, results))
379 {
380 found = true;
381 break;
382 }
383 }
384 if (!found)
385 {
386 return false;
387 }
388 if (results.size() == 0)
389 return false;
390 //Look first for a Client VM
391 for (unsigned int i=0 ; i<results.size() ; i++)
392 {
393 String s = results[i];
394 if (s.find("client") != s.npos)
395 {
396 result = s;
397 return true;
398 }
399 }
400 //else default to the first
401 result = results[0];
402 return true;
403 }
407 static CreateVMFunc getCreateVMFunc()
408 {
409 String libname;
410 if (!findJVM(libname))
411 {
412 err("No Java VM found. Is JAVA_HOME defined? Need to find 'libjvm.so'");
413 return NULL;
414 }
415 void *lib = dlopen(libname.c_str(), RTLD_NOW);
416 if (!lib)
417 {
418 err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
419 return NULL;
420 }
421 CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
422 if (!createVM)
423 {
424 err("Could not find 'JNI_CreateJavaVM' in shared library");
425 return NULL;
426 }
427 return createVM;
428 }
431 static void getJavaRoot(String &javaroot)
432 {
433 javaroot = INKSCAPE_JAVADIR;
434 }
436 #endif
442 static void populateClassPath(const String &javaroot,
443 String &result)
444 {
445 String classdir = javaroot;
446 classdir.append(DIR_SEPARATOR);
447 classdir.append("classes");
449 String cp = classdir;
451 String libdir = javaroot;
452 libdir.append(DIR_SEPARATOR);
453 libdir.append("lib");
455 DIR *dir = opendir(libdir.c_str());
456 if (!dir)
457 {
458 result = cp;
459 return;
460 }
462 while (true)
463 {
464 struct dirent *de = readdir(dir);
465 if (!de)
466 break;
467 String fname = de->d_name;
468 if (fname == "." || fname == "..")
469 continue;
470 if (fname.size()<5) //x.jar
471 continue;
472 if (fname.compare(fname.size()-4, 4, ".jar") != 0)
473 continue;
475 String path = libdir;
476 path.append(DIR_SEPARATOR);
477 path.append(fname);
479 cp.append(PATH_SEPARATOR);
480 cp.append(path);
481 }
482 closedir(dir);
484 result = cp;
486 return;
487 }
490 static void stdOutWrite(jlong ptr, jint ch)
491 {
492 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
493 bind->stdOut(ch);
494 }
496 static void stdErrWrite(jlong ptr, jint ch)
497 {
498 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
499 bind->stdErr(ch);
500 }
503 static JNINativeMethod scriptRunnerMethods[] =
504 {
505 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
506 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
507 { NULL, NULL, NULL }
508 };
510 bool JavaBinderyImpl::loadJVM()
511 {
512 if (jvm)
513 return true;
515 CreateVMFunc createVM = getCreateVMFunc();
516 if (!createVM)
517 {
518 err("Could not find 'JNI_CreateJavaVM' in shared library");
519 return false;
520 }
522 String javaroot;
523 getJavaRoot(javaroot);
524 String cp;
525 populateClassPath(javaroot, cp);
526 String classpath = "-Djava.class.path=";
527 classpath.append(cp);
528 msg("Class path is: '%s'", classpath.c_str());
530 String libpath = "-Djava.library.path=";
531 libpath.append(javaroot);
532 libpath.append(DIR_SEPARATOR);
533 libpath.append("libm");
534 msg("Lib path is: '%s'", libpath.c_str());
536 JavaVMInitArgs vm_args;
537 JavaVMOption options[2];
538 options[0].optionString = (char *)classpath.c_str();
539 options[1].optionString = (char *)libpath.c_str();
540 vm_args.version = JNI_VERSION_1_2;
541 vm_args.options = options;
542 vm_args.nOptions = 2;
543 vm_args.ignoreUnrecognized = true;
545 if (createVM(&jvm, &env, &vm_args) < 0)
546 {
547 err("JNI_GetDefaultJavaVMInitArgs() failed");
548 return false;
549 }
551 if (!registerNatives("org/inkscape/cmn/ScriptRunner",
552 scriptRunnerMethods))
553 {
554 return false;
555 }
556 return true;
557 }
562 bool JavaBinderyImpl::callStatic(int type,
563 const String &className,
564 const String &methodName,
565 const String &signature,
566 const std::vector<Value> ¶ms,
567 Value &retval)
568 {
569 jclass cls = env->FindClass(className.c_str());
570 if (!cls)
571 {
572 err("Could not find class '%s'", className.c_str());
573 return false;
574 }
575 jmethodID mid = env->GetStaticMethodID(cls,
576 methodName.c_str(), signature.c_str());
577 if (!mid)
578 {
579 err("Could not find method '%s:%s/%s'", className.c_str(),
580 methodName.c_str(), signature.c_str());
581 return false;
582 }
583 /**
584 * Assemble your parameters into a form usable by JNI
585 */
586 jvalue *jvals = new jvalue[params.size()];
587 for (unsigned int i=0 ; i<params.size() ; i++)
588 {
589 Value v = params[i];
590 switch (v.getType())
591 {
592 case Value::BIND_BOOLEAN:
593 {
594 jvals[i].z = (jboolean)v.getBoolean();
595 break;
596 }
597 case Value::BIND_INT:
598 {
599 jvals[i].i = (jint)v.getInt();
600 break;
601 }
602 case Value::BIND_DOUBLE:
603 {
604 jvals[i].d = (jdouble)v.getDouble();
605 break;
606 }
607 case Value::BIND_STRING:
608 {
609 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
610 break;
611 }
612 default:
613 {
614 err("Unknown value type: %d", v.getType());
615 return false;
616 }
617 }
618 }
619 switch (type)
620 {
621 case Value::BIND_VOID:
622 {
623 env->CallStaticVoidMethodA(cls, mid, jvals);
624 break;
625 }
626 case Value::BIND_BOOLEAN:
627 {
628 env->CallStaticBooleanMethodA(cls, mid, jvals);
629 break;
630 }
631 case Value::BIND_INT:
632 {
633 env->CallStaticIntMethodA(cls, mid, jvals);
634 break;
635 }
636 case Value::BIND_DOUBLE:
637 {
638 env->CallStaticDoubleMethodA(cls, mid, jvals);
639 break;
640 }
641 case Value::BIND_STRING:
642 {
643 env->CallStaticObjectMethodA(cls, mid, jvals);
644 break;
645 }
646 default:
647 {
648 err("Unknown return type: %d", type);
649 return false;
650 }
651 }
652 delete jvals;
653 return true;
654 }
659 bool JavaBinderyImpl::callMain(const String &className)
660 {
661 std::vector<Value> parms;
662 Value retval;
663 return callStatic(Value::BIND_VOID, className, "main",
664 "([Ljava/lang/String;)V", parms, retval);
665 }
669 bool JavaBinderyImpl::registerNatives(const String &className,
670 const JNINativeMethod *methods)
671 {
672 jclass cls = env->FindClass(className.c_str());
673 if (!cls)
674 {
675 err("Could not find class '%s'", className.c_str());
676 return false;
677 }
678 int nrMethods = 0;
679 for (const JNINativeMethod *m = methods ; m->name ; m++)
680 nrMethods++;
681 if (env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods) < 0)
682 {
683 err("Could not register natives");
684 return false;
685 }
686 return true;
687 }
692 } // namespace Bind
693 } // namespace Inkscape
695 //########################################################################
696 //# E N D O F F I L E
697 //########################################################################