3d68bdc15872d36b3f1be50b6c3196fde1ad22eb
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>
57 /**
58 * Note: We must limit Java or JVM-specific code to this file
59 * and to dobinding.cpp. It should be hidden from javabind.h
60 */
63 namespace Inkscape
64 {
66 namespace Bind
67 {
70 //########################################################################
71 //# DEFINITIONS
72 //########################################################################
74 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
78 //########################################################################
79 //# UTILITY
80 //########################################################################
82 /**
83 * Normalize path. Java wants '/', even on Windows
84 */
85 String normalizePath(const String &str)
86 {
87 String buf;
88 for (unsigned int i=0 ; i<str.size() ; i++)
89 {
90 char ch = str[i];
91 if (ch == '\\')
92 buf.push_back('/');
93 else
94 buf.push_back(ch);
95 }
96 return buf;
97 }
100 String getException(JNIEnv *env)
101 {
102 String buf;
103 jthrowable exc = env->ExceptionOccurred();
104 if (!exc)
105 return buf;
106 jclass cls = env->GetObjectClass(exc);
107 jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
108 jstring jstr = (jstring) env->CallObjectMethod(exc, mid);
109 const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
110 buf.append(str);
111 env->ReleaseStringUTFChars(jstr, str);
112 env->ExceptionClear();
113 return buf;
114 }
116 jint getInt(JNIEnv *env, jobject obj, const char *name)
117 {
118 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
119 return env->GetIntField(obj, fid);
120 }
122 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
123 {
124 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
125 env->SetIntField(obj, fid, val);
126 }
128 jlong getLong(JNIEnv *env, jobject obj, const char *name)
129 {
130 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
131 return env->GetLongField(obj, fid);
132 }
134 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
135 {
136 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
137 env->SetLongField(obj, fid, val);
138 }
140 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
141 {
142 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
143 return env->GetFloatField(obj, fid);
144 }
146 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
147 {
148 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
149 env->SetFloatField(obj, fid, val);
150 }
152 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
153 {
154 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
155 return env->GetDoubleField(obj, fid);
156 }
158 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
159 {
160 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
161 env->SetDoubleField(obj, fid, val);
162 }
164 String getString(JNIEnv *env, jobject obj, const char *name)
165 {
166 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
167 jstring jstr = (jstring)env->GetObjectField(obj, fid);
168 const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
169 String str = chars;
170 env->ReleaseStringUTFChars(jstr, chars);
171 return str;
172 }
174 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
175 {
176 jstring jstr = env->NewStringUTF(val.c_str());
177 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
178 env->SetObjectField(obj, fid, jstr);
179 }
184 //########################################################################
185 //# CONSTRUCTOR/DESTRUCTOR
186 //########################################################################
188 static JavaBinderyImpl *_instance = NULL;
190 JavaBindery *JavaBindery::getInstance()
191 {
192 return JavaBinderyImpl::getInstance();
193 }
195 JavaBinderyImpl *JavaBinderyImpl::getInstance()
196 {
197 if (!_instance)
198 {
199 _instance = new JavaBinderyImpl();
200 }
201 return _instance;
202 }
204 JavaBinderyImpl::JavaBinderyImpl()
205 {
206 jvm = NULL;
207 env = NULL;
208 }
210 JavaBinderyImpl::~JavaBinderyImpl()
211 {
212 }
215 //########################################################################
216 //# MESSAGES
217 //########################################################################
219 void err(const char *fmt, ...)
220 {
221 #if 0
222 va_list args;
223 fprintf(stderr, "JavaBinderyImpl err:");
224 va_start(args, fmt);
225 vfprintf(stderr, fmt, args);
226 va_end(args);
227 fprintf(stderr, "\n");
228 #else
229 va_list args;
230 g_warning("JavaBinderyImpl err:");
231 va_start(args, fmt);
232 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
233 va_end(args);
234 g_warning("\n");
235 #endif
236 }
238 void msg(const char *fmt, ...)
239 {
240 #if 0
241 va_list args;
242 fprintf(stdout, "JavaBinderyImpl:");
243 va_start(args, fmt);
244 vfprintf(stdout, fmt, args);
245 va_end(args);
246 fprintf(stdout, "\n");
247 #else
248 va_list args;
249 g_message("JavaBinderyImpl:");
250 va_start(args, fmt);
251 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
252 va_end(args);
253 g_message("\n");
254 #endif
255 }
259 //########################################################################
260 //# W I N 3 2 S T Y L E
261 //########################################################################
262 #ifdef __WIN32__
265 #define DIR_SEPARATOR "\\"
266 #define PATH_SEPARATOR ";"
270 static bool getRegistryString(HKEY root, const char *keyName,
271 const char *valName, char *buf, int buflen)
272 {
273 HKEY key;
274 DWORD bufsiz = buflen;
275 RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
276 int ret = RegQueryValueEx(key, TEXT(valName),
277 NULL, NULL, (BYTE *)buf, &bufsiz);
278 if (ret != ERROR_SUCCESS)
279 {
280 err("Key '%s\\%s not found\n", keyName, valName);
281 return false;
282 }
283 RegCloseKey(key);
284 return true;
285 }
288 static CreateVMFunc getCreateVMFunc()
289 {
290 char verbuf[16];
291 char regpath[80];
292 strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
293 bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
294 regpath, "CurrentVersion", verbuf, 15);
295 if (!ret)
296 {
297 err("JVM CurrentVersion not found in registry\n");
298 return NULL;
299 }
300 strcat(regpath, "\\");
301 strcat(regpath, verbuf);
302 //msg("reg path: %s\n", regpath);
303 char libname[80];
304 ret = getRegistryString(HKEY_LOCAL_MACHINE,
305 regpath, "RuntimeLib", libname, 79);
306 if (!ret)
307 {
308 err("Current JVM RuntimeLib not found in registry\n");
309 return NULL;
310 }
311 //msg("jvm path: %s\n", libname);
312 HMODULE lib = LoadLibrary(libname);
313 if (!lib)
314 {
315 err("Java VM not found at '%s'", libname);
316 return NULL;
317 }
318 CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
319 if (!createVM)
320 {
321 err("Could not find 'JNI_CreateJavaVM' in shared library");
322 return NULL;
323 }
324 return createVM;
325 }
327 static void getJavaRoot(String &javaroot)
328 {
329 char exeName[80];
330 GetModuleFileName(NULL, exeName, 80);
331 char *slashPos = strrchr(exeName, '\\');
332 if (slashPos)
333 *slashPos = '\0';
334 javaroot = exeName;
335 javaroot.append("\\");
336 javaroot.append(INKSCAPE_JAVADIR);
337 }
342 //########################################################################
343 //# U N I X S T Y L E
344 //########################################################################
345 #else /* !__WIN32__ */
348 #define DIR_SEPARATOR "/"
349 #define PATH_SEPARATOR ":"
352 /**
353 * Recursively descend into a directory looking for libjvm.so
354 */
355 static bool findJVMRecursive(const String &dirpath,
356 std::vector<String> &results)
357 {
358 DIR *dir = opendir(dirpath.c_str());
359 if (!dir)
360 return false;
361 bool ret = false;
362 while (true)
363 {
364 struct dirent *de = readdir(dir);
365 if (!de)
366 break;
367 String fname = de->d_name;
368 if (fname == "." || fname == "..")
369 continue;
370 String path = dirpath;
371 path.push_back('/');
372 path.append(fname);
373 if (fname == "libjvm.so")
374 {
375 ret = true;
376 results.push_back(path);
377 continue;
378 }
379 struct stat finfo;
380 if (lstat(path.c_str(), &finfo)<0)
381 {
382 break;
383 }
384 if (finfo.st_mode & S_IFDIR)
385 {
386 ret |= findJVMRecursive(path, results);
387 }
388 }
389 closedir(dir);
390 return ret;
391 }
394 static const char *commonJavaPaths[] =
395 {
396 "/usr/java",
397 "/usr/local/java",
398 "/usr/lib/jvm",
399 "/usr/local/lib/jvm",
400 NULL
401 };
403 /**
404 * Look for a Java VM (libjvm.so) in several Unix places
405 */
406 static bool findJVM(String &result)
407 {
408 std::vector<String> results;
409 int found = false;
411 /* Is there one specified by the user? */
412 const char *javaHome = getenv("JAVA_HOME");
413 if (javaHome && findJVMRecursive(javaHome, results))
414 found = true;
415 else for (const char **path = commonJavaPaths ; *path ; path++)
416 {
417 if (findJVMRecursive(*path, results))
418 {
419 found = true;
420 break;
421 }
422 }
423 if (!found)
424 {
425 return false;
426 }
427 if (results.size() == 0)
428 return false;
429 //Look first for a Client VM
430 for (unsigned int i=0 ; i<results.size() ; i++)
431 {
432 String s = results[i];
433 if (s.find("client") != s.npos)
434 {
435 result = s;
436 return true;
437 }
438 }
439 //else default to the first
440 result = results[0];
441 return true;
442 }
446 static CreateVMFunc getCreateVMFunc()
447 {
448 String libname;
449 if (!findJVM(libname))
450 {
451 err("No Java VM found. Is JAVA_HOME defined? Need to find 'libjvm.so'");
452 return NULL;
453 }
454 void *lib = dlopen(libname.c_str(), RTLD_NOW);
455 if (!lib)
456 {
457 err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
458 return NULL;
459 }
460 CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
461 if (!createVM)
462 {
463 err("Could not find 'JNI_CreateJavaVM' in shared library");
464 return NULL;
465 }
466 return createVM;
467 }
470 static void getJavaRoot(String &javaroot)
471 {
472 javaroot = INKSCAPE_JAVADIR;
473 }
475 #endif /* !__WIN32__ */
478 //########################################################################
479 //# COMMON
480 //########################################################################
483 bool JavaBinderyImpl::isLoaded()
484 {
485 return (jvm != (void *)0);
486 }
490 /**
491 * This will set up the classpath for the launched VM.
492 * We will add two things:
493 * 1. INKSCAPE_JAVADIR/classes -- path to loose classes
494 * 2. A concatenation of all jar files in INKSCAPE_JAVADIR/lib
495 *
496 * This will allow people to add classes and jars to the JVM without
497 * needing to state them explicitly.
498 *
499 * @param javaroot. Should be INKSCAPE_JAVADIR
500 * @param result a string buffer to hold the result of this method
501 */
502 static void populateClassPath(const String &javaroot,
503 String &result)
504 {
505 String classdir = javaroot;
506 classdir.append(DIR_SEPARATOR);
507 classdir.append("classes");
509 String cp = classdir;
511 String libdir = javaroot;
512 libdir.append(DIR_SEPARATOR);
513 libdir.append("lib");
515 DIR *dir = opendir(libdir.c_str());
516 if (!dir)
517 {
518 result = cp;
519 return;
520 }
522 while (true)
523 {
524 struct dirent *de = readdir(dir);
525 if (!de)
526 break;
527 String fname = de->d_name;
528 if (fname == "." || fname == "..")
529 continue;
530 if (fname.size()<5) //x.jar
531 continue;
532 if (fname.compare(fname.size()-4, 4, ".jar") != 0)
533 continue;
535 String path = libdir;
536 path.append(DIR_SEPARATOR);
537 path.append(fname);
539 cp.append(PATH_SEPARATOR);
540 cp.append(path);
541 }
542 closedir(dir);
544 result = cp;
545 }
549 //========================================================================
550 // Native methods
551 //========================================================================
552 /**
553 * These methods are used to allow the ScriptRunner class to
554 * redirect its stderr and stdout streams to here, to be caught
555 * by two string buffers. We can then use those buffers how we
556 * want. These native methods are only those needed for running
557 * a script. For the main C++/Java bindings, see dobinding.cpp
558 */
559 void JNICALL stdOutWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
560 {
561 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
562 bind->stdOut(ch);
563 }
565 void JNICALL stdErrWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
566 {
567 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
568 bind->stdErr(ch);
569 }
572 static JNINativeMethod scriptRunnerMethods[] =
573 {
574 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
575 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
576 { NULL, NULL, NULL }
577 };
578 //========================================================================
579 // End native methods
580 //========================================================================
583 /**
584 * This is used to grab output from the VM itself. See 'options' below.
585 */
586 static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args)
587 {
588 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
589 }
592 /**
593 * This is the most important part of this class. Here we
594 * attempt to find, load, and initialize a java (or mlvm?) virtual
595 * machine.
596 *
597 * @return true if successful, else false
598 */
599 bool JavaBinderyImpl::loadJVM()
600 {
601 if (jvm)
602 return true;
604 CreateVMFunc createVM = getCreateVMFunc();
605 if (!createVM)
606 {
607 err("Could not find 'JNI_CreateJavaVM' in shared library");
608 return false;
609 }
611 String javaroot;
612 getJavaRoot(javaroot);
613 String cp;
614 populateClassPath(javaroot, cp);
615 String classpath = "-Djava.class.path=";
616 classpath.append(normalizePath(cp));
617 msg("Class path is: '%s'", classpath.c_str());
619 String libpath = "-Djava.library.path=";
620 libpath.append(javaroot);
621 libpath.append(DIR_SEPARATOR);
622 libpath.append("libm");
623 libpath = normalizePath(libpath);
624 msg("Lib path is: '%s'", libpath.c_str());
626 JavaVMInitArgs vm_args;
627 JavaVMOption options[4];
628 options[0].optionString = (char *)classpath.c_str();
629 options[1].optionString = (char *)libpath.c_str();
630 options[2].optionString = (char *)"-verbose:jni";
631 options[3].optionString = (char *)"vfprintf";
632 options[3].extraInfo = (void *)vfprintfHook;
633 vm_args.version = JNI_VERSION_1_4;
634 vm_args.options = options;
635 vm_args.nOptions = 4;
636 vm_args.ignoreUnrecognized = true;
638 if (createVM(&jvm, &env, &vm_args) < 0)
639 {
640 err("JNI_CreateJavaVM() failed");
641 return false;
642 }
644 //get jvm version
645 jint vers = env->GetVersion();
646 int versionMajor = (vers>>16) & 0xffff;
647 int versionMinor = (vers ) & 0xffff;
648 msg("Loaded JVM version %d.%d", versionMajor, versionMinor);
650 if (!registerNatives("org/inkscape/cmn/ScriptRunner",
651 scriptRunnerMethods))
652 {
653 return false;
654 }
655 return true;
656 }
660 /**
661 * This is a difficult method. What we are doing is trying to
662 * call a static method with a list of arguments. Similar to
663 * a varargs call, we need to marshal the Values into their
664 * Java equivalents and make the proper call.
665 *
666 * @param type the return type of the method
667 * @param className the full (package / name) name of the java class
668 * @param methodName the name of the method being invoked
669 * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
670 * that describes the param and return types of the method.
671 * @param retval the return value of the java method
672 * @return true if the call was successful, else false. This is not
673 * the return value of the method.
674 */
675 bool JavaBinderyImpl::callStatic(int type,
676 const String &className,
677 const String &methodName,
678 const String &signature,
679 const std::vector<Value> ¶ms,
680 Value &retval)
681 {
682 jclass cls = env->FindClass(className.c_str());
683 if (!cls)
684 {
685 err("Could not find class '%s'", className.c_str());
686 return false;
687 }
688 jmethodID mid = env->GetStaticMethodID(cls,
689 methodName.c_str(), signature.c_str());
690 if (!mid)
691 {
692 err("Could not find method '%s:%s/%s'", className.c_str(),
693 methodName.c_str(), signature.c_str());
694 return false;
695 }
696 /**
697 * Assemble your parameters into a form usable by JNI
698 */
699 jvalue *jvals = new jvalue[params.size()];
700 for (unsigned int i=0 ; i<params.size() ; i++)
701 {
702 Value v = params[i];
703 switch (v.getType())
704 {
705 case Value::BIND_BOOLEAN:
706 {
707 jvals[i].z = (jboolean)v.getBoolean();
708 break;
709 }
710 case Value::BIND_INT:
711 {
712 jvals[i].i = (jint)v.getInt();
713 break;
714 }
715 case Value::BIND_DOUBLE:
716 {
717 jvals[i].d = (jdouble)v.getDouble();
718 break;
719 }
720 case Value::BIND_STRING:
721 {
722 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
723 break;
724 }
725 default:
726 {
727 err("Unknown value type: %d", v.getType());
728 return false;
729 }
730 }
731 }
732 switch (type)
733 {
734 case Value::BIND_VOID:
735 {
736 env->CallStaticVoidMethodA(cls, mid, jvals);
737 break;
738 }
739 case Value::BIND_BOOLEAN:
740 {
741 env->CallStaticBooleanMethodA(cls, mid, jvals);
742 break;
743 }
744 case Value::BIND_INT:
745 {
746 env->CallStaticIntMethodA(cls, mid, jvals);
747 break;
748 }
749 case Value::BIND_DOUBLE:
750 {
751 env->CallStaticDoubleMethodA(cls, mid, jvals);
752 break;
753 }
754 case Value::BIND_STRING:
755 {
756 env->CallStaticObjectMethodA(cls, mid, jvals);
757 break;
758 }
759 default:
760 {
761 err("Unknown return type: %d", type);
762 return false;
763 }
764 }
765 delete jvals;
766 return true;
767 }
771 /**
772 * Convenience method to call the static void main(String argv[])
773 * method of a given class
774 *
775 * @param className full name of the java class
776 * @return true if successful, else false
777 */
778 bool JavaBinderyImpl::callMain(const String &className)
779 {
780 std::vector<Value> parms;
781 Value retval;
782 return callStatic(Value::BIND_VOID, className, "main",
783 "([Ljava/lang/String;)V", parms, retval);
784 }
787 /**
788 * Used to register an array of native methods for a named class
789 *
790 * @param className the full name of the java class
791 * @param the method array
792 * @return true if successful, else false
793 */
794 bool JavaBinderyImpl::registerNatives(const String &className,
795 const JNINativeMethod *methods)
796 {
797 jclass cls = env->FindClass(className.c_str());
798 if (!cls)
799 {
800 err("Could not find class '%s'", className.c_str());
801 return false;
802 }
803 msg("registerNatives: class '%s' found", className.c_str());
805 /**
806 * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
807 */
808 jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
809 "()[Ljava/lang/reflect/Constructor;");
810 if (!mid)
811 {
812 err("Could not get reflect mid for 'getConstructors' : %s",
813 getException(env).c_str());
814 return false;
815 }
816 jobject res = env->CallObjectMethod(cls, mid);
817 if (!res)
818 {
819 err("Could not get constructors");
820 return false;
821 }
822 /**
823 * end hack
824 */
825 jint nrMethods = 0;
826 for (const JNINativeMethod *m = methods ; m->name ; m++)
827 nrMethods++;
828 jint ret = env->RegisterNatives(cls, methods, nrMethods);
829 if (ret < 0)
830 {
831 err("Could not register %d native methods for '%s' : %s",
832 nrMethods, className.c_str(), getException(env).c_str());
833 return false;
834 }
835 return true;
836 }
841 } // namespace Bind
842 } // namespace Inkscape
844 //########################################################################
845 //# E N D O F F I L E
846 //########################################################################