a25be353eb5ccdfb241aa194934f6046ea888029
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 */
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <string.h>
30 #include <jni.h>
32 #include <sys/types.h>
33 #include <dirent.h>
36 #ifdef __WIN32__
37 #include <windows.h>
38 #else
39 #include <dlfcn.h>
40 #include <errno.h>
41 #endif
43 #include "javabind.h"
44 #include "javabind-private.h"
45 #include <prefix.h>
46 #include <glib/gmessages.h>
52 namespace Inkscape
53 {
55 namespace Bind
56 {
59 //########################################################################
60 //# DEFINITIONS
61 //########################################################################
63 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
67 //########################################################################
68 //# UTILITY
69 //########################################################################
71 jint getInt(JNIEnv *env, jobject obj, const char *name)
72 {
73 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
74 return env->GetIntField(obj, fid);
75 }
77 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
78 {
79 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
80 env->SetIntField(obj, fid, val);
81 }
83 jlong getLong(JNIEnv *env, jobject obj, const char *name)
84 {
85 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
86 return env->GetLongField(obj, fid);
87 }
89 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
90 {
91 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
92 env->SetLongField(obj, fid, val);
93 }
95 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
96 {
97 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
98 return env->GetFloatField(obj, fid);
99 }
101 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
102 {
103 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
104 env->SetFloatField(obj, fid, val);
105 }
107 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
108 {
109 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
110 return env->GetDoubleField(obj, fid);
111 }
113 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
114 {
115 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
116 env->SetDoubleField(obj, fid, val);
117 }
119 String getString(JNIEnv *env, jobject obj, const char *name)
120 {
121 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
122 jstring jstr = (jstring)env->GetObjectField(obj, fid);
123 const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
124 String str = chars;
125 env->ReleaseStringUTFChars(jstr, chars);
126 return str;
127 }
129 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
130 {
131 jstring jstr = env->NewStringUTF(val.c_str());
132 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
133 env->SetObjectField(obj, fid, jstr);
134 }
139 //########################################################################
140 //# CONSTRUCTOR/DESTRUCTOR
141 //########################################################################
143 static JavaBinderyImpl *_instance = NULL;
145 JavaBindery *JavaBindery::getInstance()
146 {
147 return JavaBinderyImpl::getInstance();
148 }
150 JavaBinderyImpl *JavaBinderyImpl::getInstance()
151 {
152 if (!_instance)
153 {
154 _instance = new JavaBinderyImpl();
155 }
156 return _instance;
157 }
159 JavaBinderyImpl::JavaBinderyImpl()
160 {
161 jvm = NULL;
162 env = NULL;
163 }
165 JavaBinderyImpl::~JavaBinderyImpl()
166 {
167 }
169 void err(const char *fmt, ...)
170 {
171 #if 0
172 va_list args;
173 fprintf(stderr, "JavaBinderyImpl err:");
174 va_start(args, fmt);
175 vfprintf(stderr, fmt, args);
176 va_end(args);
177 fprintf(stderr, "\n");
178 #else
179 va_list args;
180 g_warning("JavaBinderyImpl err:");
181 va_start(args, fmt);
182 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
183 va_end(args);
184 g_warning("\n");
185 #endif
186 }
188 void msg(const char *fmt, ...)
189 {
190 #if 0
191 va_list args;
192 fprintf(stdout, "JavaBinderyImpl:");
193 va_start(args, fmt);
194 vfprintf(stdout, fmt, args);
195 va_end(args);
196 fprintf(stdout, "\n");
197 #else
198 va_list args;
199 g_message("JavaBinderyImpl:");
200 va_start(args, fmt);
201 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
202 va_end(args);
203 g_message("\n");
204 #endif
205 }
207 bool JavaBinderyImpl::isLoaded()
208 {
209 return (jvm != (void *)0);
210 }
214 #ifdef __WIN32__
217 //########################################################################
218 //# W I N 3 2 S T Y L E
219 //########################################################################
222 #define DIR_SEPARATOR "\\"
223 #define PATH_SEPARATOR ";"
227 static bool getRegistryString(HKEY root, const char *keyName,
228 const char *valName, char *buf, int buflen)
229 {
230 HKEY key;
231 DWORD bufsiz = buflen;
232 RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
233 int ret = RegQueryValueEx(key, TEXT(valName),
234 NULL, NULL, (BYTE *)buf, &bufsiz);
235 if (ret != ERROR_SUCCESS)
236 {
237 err("Key '%s\\%s not found\n", keyName, valName);
238 return false;
239 }
240 RegCloseKey(key);
241 return true;
242 }
245 static CreateVMFunc getCreateVMFunc()
246 {
247 char verbuf[16];
248 char regpath[80];
249 strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
250 bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
251 regpath, "CurrentVersion", verbuf, 15);
252 if (!ret)
253 {
254 err("JVM CurrentVersion not found in registry\n");
255 return NULL;
256 }
257 strcat(regpath, "\\");
258 strcat(regpath, verbuf);
259 //msg("reg path: %s\n", regpath);
260 char libname[80];
261 ret = getRegistryString(HKEY_LOCAL_MACHINE,
262 regpath, "RuntimeLib", libname, 79);
263 if (!ret)
264 {
265 err("Current JVM RuntimeLib not found in registry\n");
266 return NULL;
267 }
268 //msg("jvm path: %s\n", libname);
269 HMODULE lib = LoadLibrary(libname);
270 if (!lib)
271 {
272 err("Java VM not found at '%s'", libname);
273 return NULL;
274 }
275 CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
276 if (!createVM)
277 {
278 err("Could not find 'JNI_CreateJavaVM' in shared library");
279 return NULL;
280 }
281 return createVM;
282 }
284 static void getJavaRoot(String &javaroot)
285 {
286 char exeName[80];
287 GetModuleFileName(NULL, exeName, 80);
288 char *slashPos = strrchr(exeName, '\\');
289 if (slashPos)
290 *slashPos = '\0';
291 javaroot = exeName;
292 javaroot.append("\\share\\java");
293 }
296 #else
299 //########################################################################
300 //# U N I X S T Y L E
301 //########################################################################
304 #define DIR_SEPARATOR "/"
305 #define PATH_SEPARATOR ":"
308 /**
309 * Recursively descend into a directory looking for libjvm.so
310 */
311 static bool findJVMRecursive(const String &dirpath,
312 std::vector<String> &results)
313 {
314 DIR *dir = opendir(dirpath.c_str());
315 if (!dir)
316 return false;
317 bool ret = false;
318 while (true)
319 {
320 struct dirent *de = readdir(dir);
321 if (!de)
322 break;
323 String fname = de->d_name;
324 if (fname == "." || fname == "..")
325 continue;
326 String path = dirpath;
327 path.push_back('/');
328 path.append(fname);
329 if (fname == "libjvm.so")
330 {
331 ret = true;
332 results.push_back(path);
333 continue;
334 }
335 struct stat finfo;
336 if (lstat(path.c_str(), &finfo)<0)
337 {
338 break;
339 }
340 if (finfo.st_mode & S_IFDIR)
341 {
342 ret |= findJVMRecursive(path, results);
343 }
344 }
345 closedir(dir);
346 return ret;
347 }
350 static const char *commonJavaPaths[] =
351 {
352 "/usr/java",
353 "/usr/local/java",
354 "/usr/lib/jvm",
355 "/usr/local/lib/jvm",
356 NULL
357 };
359 /**
360 * Look for a Java VM (libjvm.so) in several Unix places
361 */
362 static bool findJVM(String &result)
363 {
364 std::vector<String> results;
365 int found = false;
367 /* Is there one specified by the user? */
368 const char *javaHome = getenv("JAVA_HOME");
369 if (javaHome && findJVMRecursive(javaHome, results))
370 found = true;
371 else for (const char **path = commonJavaPaths ; *path ; path++)
372 {
373 if (findJVMRecursive(*path, results))
374 {
375 found = true;
376 break;
377 }
378 }
379 if (!found)
380 {
381 return false;
382 }
383 if (results.size() == 0)
384 return false;
385 //Look first for a Client VM
386 for (unsigned int i=0 ; i<results.size() ; i++)
387 {
388 String s = results[i];
389 if (s.find("client") != s.npos)
390 {
391 result = s;
392 return true;
393 }
394 }
395 //else default to the first
396 result = results[0];
397 return true;
398 }
402 static CreateVMFunc getCreateVMFunc()
403 {
404 String libname;
405 if (!findJVM(libname))
406 {
407 err("No Java VM found. Is JAVA_HOME defined? Need to find 'libjvm.so'");
408 return NULL;
409 }
410 void *lib = dlopen(libname.c_str(), RTLD_NOW);
411 if (!lib)
412 {
413 err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
414 return NULL;
415 }
416 CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
417 if (!createVM)
418 {
419 err("Could not find 'JNI_CreateJavaVM' in shared library");
420 return NULL;
421 }
422 return createVM;
423 }
426 static void getJavaRoot(String &javaroot)
427 {
428 javaroot = BR_DATADIR("/java");
429 }
431 #endif
437 static void populateClassPath(const String &javaroot,
438 String &result)
439 {
440 String classdir = javaroot;
441 classdir.append(DIR_SEPARATOR);
442 classdir.append("classes");
444 String cp = classdir;
446 String libdir = javaroot;
447 libdir.append(DIR_SEPARATOR);
448 libdir.append("lib");
450 DIR *dir = opendir(libdir.c_str());
451 if (!dir)
452 {
453 result = cp;
454 return;
455 }
457 while (true)
458 {
459 struct dirent *de = readdir(dir);
460 if (!de)
461 break;
462 String fname = de->d_name;
463 if (fname == "." || fname == "..")
464 continue;
465 if (fname.size()<5) //x.jar
466 continue;
467 if (fname.compare(fname.size()-4, 4, ".jar") != 0)
468 continue;
470 String path = libdir;
471 path.append(DIR_SEPARATOR);
472 path.append(fname);
474 cp.append(PATH_SEPARATOR);
475 cp.append(path);
476 }
477 closedir(dir);
479 result = cp;
481 return;
482 }
485 static void stdOutWrite(jlong ptr, jint ch)
486 {
487 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
488 bind->stdOut(ch);
489 }
491 static void stdErrWrite(jlong ptr, jint ch)
492 {
493 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
494 bind->stdErr(ch);
495 }
498 static JNINativeMethod scriptRunnerMethods[] =
499 {
500 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
501 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
502 { NULL, NULL, NULL }
503 };
505 bool JavaBinderyImpl::loadJVM()
506 {
507 if (jvm)
508 return true;
510 CreateVMFunc createVM = getCreateVMFunc();
511 if (!createVM)
512 {
513 err("Could not find 'JNI_CreateJavaVM' in shared library");
514 return false;
515 }
517 String javaroot;
518 getJavaRoot(javaroot);
519 String cp;
520 populateClassPath(javaroot, cp);
521 String classpath = "-Djava.class.path=";
522 classpath.append(cp);
523 msg("Class path is: '%s'", classpath.c_str());
525 String libpath = "-Djava.library.path=";
526 libpath.append(javaroot);
527 libpath.append(DIR_SEPARATOR);
528 libpath.append("libm");
529 msg("Lib path is: '%s'", libpath.c_str());
531 JavaVMInitArgs vm_args;
532 JavaVMOption options[2];
533 options[0].optionString = (char *)classpath.c_str();
534 options[1].optionString = (char *)libpath.c_str();
535 vm_args.version = JNI_VERSION_1_2;
536 vm_args.options = options;
537 vm_args.nOptions = 2;
538 vm_args.ignoreUnrecognized = true;
540 if (createVM(&jvm, &env, &vm_args) < 0)
541 {
542 err("JNI_GetDefaultJavaVMInitArgs() failed");
543 return false;
544 }
546 if (!registerNatives("org/inkscape/cmn/ScriptRunner",
547 scriptRunnerMethods))
548 {
549 return false;
550 }
551 return true;
552 }
557 bool JavaBinderyImpl::callStatic(int type,
558 const String &className,
559 const String &methodName,
560 const String &signature,
561 const std::vector<Value> ¶ms,
562 Value &retval)
563 {
564 jclass cls = env->FindClass(className.c_str());
565 if (!cls)
566 {
567 err("Could not find class '%s'", className.c_str());
568 return false;
569 }
570 jmethodID mid = env->GetStaticMethodID(cls,
571 methodName.c_str(), signature.c_str());
572 if (!mid)
573 {
574 err("Could not find method '%s:%s/%s'", className.c_str(),
575 methodName.c_str(), signature.c_str());
576 return false;
577 }
578 /**
579 * Assemble your parameters into a form usable by JNI
580 */
581 jvalue *jvals = new jvalue[params.size()];
582 for (unsigned int i=0 ; i<params.size() ; i++)
583 {
584 Value v = params[i];
585 switch (v.getType())
586 {
587 case Value::BIND_BOOLEAN:
588 {
589 jvals[i].z = (jboolean)v.getBoolean();
590 break;
591 }
592 case Value::BIND_INT:
593 {
594 jvals[i].i = (jint)v.getInt();
595 break;
596 }
597 case Value::BIND_DOUBLE:
598 {
599 jvals[i].d = (jdouble)v.getDouble();
600 break;
601 }
602 case Value::BIND_STRING:
603 {
604 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
605 break;
606 }
607 default:
608 {
609 err("Unknown value type: %d", v.getType());
610 return false;
611 }
612 }
613 }
614 switch (type)
615 {
616 case Value::BIND_VOID:
617 {
618 env->CallStaticVoidMethodA(cls, mid, jvals);
619 break;
620 }
621 case Value::BIND_BOOLEAN:
622 {
623 env->CallStaticBooleanMethodA(cls, mid, jvals);
624 break;
625 }
626 case Value::BIND_INT:
627 {
628 env->CallStaticIntMethodA(cls, mid, jvals);
629 break;
630 }
631 case Value::BIND_DOUBLE:
632 {
633 env->CallStaticDoubleMethodA(cls, mid, jvals);
634 break;
635 }
636 case Value::BIND_STRING:
637 {
638 env->CallStaticObjectMethodA(cls, mid, jvals);
639 break;
640 }
641 default:
642 {
643 err("Unknown return type: %d", type);
644 return false;
645 }
646 }
647 delete jvals;
648 return true;
649 }
654 bool JavaBinderyImpl::callMain(const String &className)
655 {
656 std::vector<Value> parms;
657 Value retval;
658 return callStatic(Value::BIND_VOID, className, "main",
659 "([Ljava/lang/String;)V", parms, retval);
660 }
664 bool JavaBinderyImpl::registerNatives(const String &className,
665 const JNINativeMethod *methods)
666 {
667 jclass cls = env->FindClass(className.c_str());
668 if (!cls)
669 {
670 err("Could not find class '%s'", className.c_str());
671 return false;
672 }
673 int nrMethods = 0;
674 for (const JNINativeMethod *m = methods ; m->name ; m++)
675 nrMethods++;
676 if (env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods) < 0)
677 {
678 err("Could not register natives");
679 return false;
680 }
681 return true;
682 }
687 } // namespace Bind
688 } // namespace Inkscape
690 //########################################################################
691 //# E N D O F F I L E
692 //########################################################################