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