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