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 jint getInt(JNIEnv *env, jobject obj, const char *name)
83 {
84 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
85 return env->GetIntField(obj, fid);
86 }
88 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
89 {
90 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
91 env->SetIntField(obj, fid, val);
92 }
94 jlong getLong(JNIEnv *env, jobject obj, const char *name)
95 {
96 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
97 return env->GetLongField(obj, fid);
98 }
100 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
101 {
102 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
103 env->SetLongField(obj, fid, val);
104 }
106 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
107 {
108 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
109 return env->GetFloatField(obj, fid);
110 }
112 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
113 {
114 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
115 env->SetFloatField(obj, fid, val);
116 }
118 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
119 {
120 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
121 return env->GetDoubleField(obj, fid);
122 }
124 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
125 {
126 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
127 env->SetDoubleField(obj, fid, val);
128 }
130 String getString(JNIEnv *env, jobject obj, const char *name)
131 {
132 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
133 jstring jstr = (jstring)env->GetObjectField(obj, fid);
134 const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
135 String str = chars;
136 env->ReleaseStringUTFChars(jstr, chars);
137 return str;
138 }
140 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
141 {
142 jstring jstr = env->NewStringUTF(val.c_str());
143 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
144 env->SetObjectField(obj, fid, jstr);
145 }
150 //########################################################################
151 //# CONSTRUCTOR/DESTRUCTOR
152 //########################################################################
154 static JavaBinderyImpl *_instance = NULL;
156 JavaBindery *JavaBindery::getInstance()
157 {
158 return JavaBinderyImpl::getInstance();
159 }
161 JavaBinderyImpl *JavaBinderyImpl::getInstance()
162 {
163 if (!_instance)
164 {
165 _instance = new JavaBinderyImpl();
166 }
167 return _instance;
168 }
170 JavaBinderyImpl::JavaBinderyImpl()
171 {
172 jvm = NULL;
173 env = NULL;
174 }
176 JavaBinderyImpl::~JavaBinderyImpl()
177 {
178 }
181 //########################################################################
182 //# MESSAGES
183 //########################################################################
185 void err(const char *fmt, ...)
186 {
187 #if 0
188 va_list args;
189 fprintf(stderr, "JavaBinderyImpl err:");
190 va_start(args, fmt);
191 vfprintf(stderr, fmt, args);
192 va_end(args);
193 fprintf(stderr, "\n");
194 #else
195 va_list args;
196 g_warning("JavaBinderyImpl err:");
197 va_start(args, fmt);
198 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
199 va_end(args);
200 g_warning("\n");
201 #endif
202 }
204 void msg(const char *fmt, ...)
205 {
206 #if 0
207 va_list args;
208 fprintf(stdout, "JavaBinderyImpl:");
209 va_start(args, fmt);
210 vfprintf(stdout, fmt, args);
211 va_end(args);
212 fprintf(stdout, "\n");
213 #else
214 va_list args;
215 g_message("JavaBinderyImpl:");
216 va_start(args, fmt);
217 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
218 va_end(args);
219 g_message("\n");
220 #endif
221 }
225 //########################################################################
226 //# W I N 3 2 S T Y L E
227 //########################################################################
228 #ifdef __WIN32__
231 #define DIR_SEPARATOR "\\"
232 #define PATH_SEPARATOR ";"
236 static bool getRegistryString(HKEY root, const char *keyName,
237 const char *valName, char *buf, int buflen)
238 {
239 HKEY key;
240 DWORD bufsiz = buflen;
241 RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
242 int ret = RegQueryValueEx(key, TEXT(valName),
243 NULL, NULL, (BYTE *)buf, &bufsiz);
244 if (ret != ERROR_SUCCESS)
245 {
246 err("Key '%s\\%s not found\n", keyName, valName);
247 return false;
248 }
249 RegCloseKey(key);
250 return true;
251 }
254 static CreateVMFunc getCreateVMFunc()
255 {
256 char verbuf[16];
257 char regpath[80];
258 strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
259 bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
260 regpath, "CurrentVersion", verbuf, 15);
261 if (!ret)
262 {
263 err("JVM CurrentVersion not found in registry\n");
264 return NULL;
265 }
266 strcat(regpath, "\\");
267 strcat(regpath, verbuf);
268 //msg("reg path: %s\n", regpath);
269 char libname[80];
270 ret = getRegistryString(HKEY_LOCAL_MACHINE,
271 regpath, "RuntimeLib", libname, 79);
272 if (!ret)
273 {
274 err("Current JVM RuntimeLib not found in registry\n");
275 return NULL;
276 }
277 //msg("jvm path: %s\n", libname);
278 HMODULE lib = LoadLibrary(libname);
279 if (!lib)
280 {
281 err("Java VM not found at '%s'", libname);
282 return NULL;
283 }
284 CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
285 if (!createVM)
286 {
287 err("Could not find 'JNI_CreateJavaVM' in shared library");
288 return NULL;
289 }
290 return createVM;
291 }
293 static void getJavaRoot(String &javaroot)
294 {
295 char exeName[80];
296 GetModuleFileName(NULL, exeName, 80);
297 char *slashPos = strrchr(exeName, '\\');
298 if (slashPos)
299 *slashPos = '\0';
300 javaroot = exeName;
301 javaroot.append("\\");
302 javaroot.append(INKSCAPE_JAVADIR);
303 }
308 //########################################################################
309 //# U N I X S T Y L E
310 //########################################################################
311 #else /* !__WIN32__ */
314 #define DIR_SEPARATOR "/"
315 #define PATH_SEPARATOR ":"
318 /**
319 * Recursively descend into a directory looking for libjvm.so
320 */
321 static bool findJVMRecursive(const String &dirpath,
322 std::vector<String> &results)
323 {
324 DIR *dir = opendir(dirpath.c_str());
325 if (!dir)
326 return false;
327 bool ret = false;
328 while (true)
329 {
330 struct dirent *de = readdir(dir);
331 if (!de)
332 break;
333 String fname = de->d_name;
334 if (fname == "." || fname == "..")
335 continue;
336 String path = dirpath;
337 path.push_back('/');
338 path.append(fname);
339 if (fname == "libjvm.so")
340 {
341 ret = true;
342 results.push_back(path);
343 continue;
344 }
345 struct stat finfo;
346 if (lstat(path.c_str(), &finfo)<0)
347 {
348 break;
349 }
350 if (finfo.st_mode & S_IFDIR)
351 {
352 ret |= findJVMRecursive(path, results);
353 }
354 }
355 closedir(dir);
356 return ret;
357 }
360 static const char *commonJavaPaths[] =
361 {
362 "/usr/java",
363 "/usr/local/java",
364 "/usr/lib/jvm",
365 "/usr/local/lib/jvm",
366 NULL
367 };
369 /**
370 * Look for a Java VM (libjvm.so) in several Unix places
371 */
372 static bool findJVM(String &result)
373 {
374 std::vector<String> results;
375 int found = false;
377 /* Is there one specified by the user? */
378 const char *javaHome = getenv("JAVA_HOME");
379 if (javaHome && findJVMRecursive(javaHome, results))
380 found = true;
381 else for (const char **path = commonJavaPaths ; *path ; path++)
382 {
383 if (findJVMRecursive(*path, results))
384 {
385 found = true;
386 break;
387 }
388 }
389 if (!found)
390 {
391 return false;
392 }
393 if (results.size() == 0)
394 return false;
395 //Look first for a Client VM
396 for (unsigned int i=0 ; i<results.size() ; i++)
397 {
398 String s = results[i];
399 if (s.find("client") != s.npos)
400 {
401 result = s;
402 return true;
403 }
404 }
405 //else default to the first
406 result = results[0];
407 return true;
408 }
412 static CreateVMFunc getCreateVMFunc()
413 {
414 String libname;
415 if (!findJVM(libname))
416 {
417 err("No Java VM found. Is JAVA_HOME defined? Need to find 'libjvm.so'");
418 return NULL;
419 }
420 void *lib = dlopen(libname.c_str(), RTLD_NOW);
421 if (!lib)
422 {
423 err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
424 return NULL;
425 }
426 CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
427 if (!createVM)
428 {
429 err("Could not find 'JNI_CreateJavaVM' in shared library");
430 return NULL;
431 }
432 return createVM;
433 }
436 static void getJavaRoot(String &javaroot)
437 {
438 javaroot = INKSCAPE_JAVADIR;
439 }
441 #endif /* !__WIN32__ */
444 //########################################################################
445 //# COMMON
446 //########################################################################
449 bool JavaBinderyImpl::isLoaded()
450 {
451 return (jvm != (void *)0);
452 }
456 /**
457 * This will set up the classpath for the launched VM.
458 * We will add two things:
459 * 1. INKSCAPE_JAVADIR/classes -- path to loose classes
460 * 2. A concatenation of all jar files in INKSCAPE_JAVADIR/lib
461 *
462 * This will allow people to add classes and jars to the JVM without
463 * needing to state them explicitly.
464 *
465 * @param javaroot. Should be INKSCAPE_JAVADIR
466 * @param result a string buffer to hold the result of this method
467 */
468 static void populateClassPath(const String &javaroot,
469 String &result)
470 {
471 String classdir = javaroot;
472 classdir.append(DIR_SEPARATOR);
473 classdir.append("classes");
475 String cp = classdir;
477 String libdir = javaroot;
478 libdir.append(DIR_SEPARATOR);
479 libdir.append("lib");
481 DIR *dir = opendir(libdir.c_str());
482 if (!dir)
483 {
484 result = cp;
485 return;
486 }
488 while (true)
489 {
490 struct dirent *de = readdir(dir);
491 if (!de)
492 break;
493 String fname = de->d_name;
494 if (fname == "." || fname == "..")
495 continue;
496 if (fname.size()<5) //x.jar
497 continue;
498 if (fname.compare(fname.size()-4, 4, ".jar") != 0)
499 continue;
501 String path = libdir;
502 path.append(DIR_SEPARATOR);
503 path.append(fname);
505 cp.append(PATH_SEPARATOR);
506 cp.append(path);
507 }
508 closedir(dir);
510 result = cp;
512 return;
513 }
517 //========================================================================
518 // Native methods
519 //========================================================================
520 /**
521 * These methods are used to allow the ScriptRunner class to
522 * redirect its stderr and stdout streams to here, to be caught
523 * by two string buffers. We can then use those buffers how we
524 * want. These native methods are only those needed for running
525 * a script. For the main C++/Java bindings, see dobinding.cpp
526 */
527 static void stdOutWrite(jlong ptr, jint ch)
528 {
529 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
530 bind->stdOut(ch);
531 }
533 static void stdErrWrite(jlong ptr, jint ch)
534 {
535 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
536 bind->stdErr(ch);
537 }
540 static JNINativeMethod scriptRunnerMethods[] =
541 {
542 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
543 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
544 { NULL, NULL, NULL }
545 };
546 //========================================================================
547 // End native methods
548 //========================================================================
553 /**
554 * This is the most important part of this class. Here we
555 * attempt to find, load, and initialize a java (or mlvm?) virtual
556 * machine.
557 *
558 * @return true if successful, else false
559 */
560 bool JavaBinderyImpl::loadJVM()
561 {
562 if (jvm)
563 return true;
565 CreateVMFunc createVM = getCreateVMFunc();
566 if (!createVM)
567 {
568 err("Could not find 'JNI_CreateJavaVM' in shared library");
569 return false;
570 }
572 String javaroot;
573 getJavaRoot(javaroot);
574 String cp;
575 populateClassPath(javaroot, cp);
576 String classpath = "-Djava.class.path=";
577 classpath.append(cp);
578 msg("Class path is: '%s'", classpath.c_str());
580 String libpath = "-Djava.library.path=";
581 libpath.append(javaroot);
582 libpath.append(DIR_SEPARATOR);
583 libpath.append("libm");
584 msg("Lib path is: '%s'", libpath.c_str());
586 JavaVMInitArgs vm_args;
587 JavaVMOption options[2];
588 options[0].optionString = (char *)classpath.c_str();
589 options[1].optionString = (char *)libpath.c_str();
590 vm_args.version = JNI_VERSION_1_2;
591 vm_args.options = options;
592 vm_args.nOptions = 2;
593 vm_args.ignoreUnrecognized = true;
595 if (createVM(&jvm, &env, &vm_args) < 0)
596 {
597 err("JNI_GetDefaultJavaVMInitArgs() failed");
598 return false;
599 }
601 if (!registerNatives("org/inkscape/cmn/ScriptRunner",
602 scriptRunnerMethods))
603 {
604 return false;
605 }
606 return true;
607 }
611 /**
612 * This is a difficult method. What we are doing is trying to
613 * call a static method with a list of arguments. Similar to
614 * a varargs call, we need to marshal the Values into their
615 * Java equivalents and make the proper call.
616 *
617 * @param type the return type of the method
618 * @param className the full (package / name) name of the java class
619 * @param methodName the name of the method being invoked
620 * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
621 * that describes the param and return types of the method.
622 * @param retval the return value of the java method
623 * @return true if the call was successful, else false. This is not
624 * the return value of the method.
625 */
626 bool JavaBinderyImpl::callStatic(int type,
627 const String &className,
628 const String &methodName,
629 const String &signature,
630 const std::vector<Value> ¶ms,
631 Value &retval)
632 {
633 jclass cls = env->FindClass(className.c_str());
634 if (!cls)
635 {
636 err("Could not find class '%s'", className.c_str());
637 return false;
638 }
639 jmethodID mid = env->GetStaticMethodID(cls,
640 methodName.c_str(), signature.c_str());
641 if (!mid)
642 {
643 err("Could not find method '%s:%s/%s'", className.c_str(),
644 methodName.c_str(), signature.c_str());
645 return false;
646 }
647 /**
648 * Assemble your parameters into a form usable by JNI
649 */
650 jvalue *jvals = new jvalue[params.size()];
651 for (unsigned int i=0 ; i<params.size() ; i++)
652 {
653 Value v = params[i];
654 switch (v.getType())
655 {
656 case Value::BIND_BOOLEAN:
657 {
658 jvals[i].z = (jboolean)v.getBoolean();
659 break;
660 }
661 case Value::BIND_INT:
662 {
663 jvals[i].i = (jint)v.getInt();
664 break;
665 }
666 case Value::BIND_DOUBLE:
667 {
668 jvals[i].d = (jdouble)v.getDouble();
669 break;
670 }
671 case Value::BIND_STRING:
672 {
673 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
674 break;
675 }
676 default:
677 {
678 err("Unknown value type: %d", v.getType());
679 return false;
680 }
681 }
682 }
683 switch (type)
684 {
685 case Value::BIND_VOID:
686 {
687 env->CallStaticVoidMethodA(cls, mid, jvals);
688 break;
689 }
690 case Value::BIND_BOOLEAN:
691 {
692 env->CallStaticBooleanMethodA(cls, mid, jvals);
693 break;
694 }
695 case Value::BIND_INT:
696 {
697 env->CallStaticIntMethodA(cls, mid, jvals);
698 break;
699 }
700 case Value::BIND_DOUBLE:
701 {
702 env->CallStaticDoubleMethodA(cls, mid, jvals);
703 break;
704 }
705 case Value::BIND_STRING:
706 {
707 env->CallStaticObjectMethodA(cls, mid, jvals);
708 break;
709 }
710 default:
711 {
712 err("Unknown return type: %d", type);
713 return false;
714 }
715 }
716 delete jvals;
717 return true;
718 }
722 /**
723 * Convenience method to call the static void main(String argv[])
724 * method of a given class
725 *
726 * @param className full name of the java class
727 * @return true if successful, else false
728 */
729 bool JavaBinderyImpl::callMain(const String &className)
730 {
731 std::vector<Value> parms;
732 Value retval;
733 return callStatic(Value::BIND_VOID, className, "main",
734 "([Ljava/lang/String;)V", parms, retval);
735 }
738 /**
739 * Used to register an array of native methods for a named class
740 *
741 * @param className the full name of the java class
742 * @param the method array
743 * @return true if successful, else false
744 */
745 bool JavaBinderyImpl::registerNatives(const String &className,
746 const JNINativeMethod *methods)
747 {
748 jclass cls = env->FindClass(className.c_str());
749 if (!cls)
750 {
751 err("Could not find class '%s'", className.c_str());
752 return false;
753 }
754 int nrMethods = 0;
755 for (const JNINativeMethod *m = methods ; m->name ; m++)
756 nrMethods++;
757 if (env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods) < 0)
758 {
759 err("Could not register natives");
760 return false;
761 }
762 return true;
763 }
768 } // namespace Bind
769 } // namespace Inkscape
771 //########################################################################
772 //# E N D O F F I L E
773 //########################################################################