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>
56 //For repr and document
57 #include <document.h>
58 #include <inkscape.h>
59 #include <xml/repr.h>
61 /**
62 * Note: We must limit Java or JVM-specific code to this file
63 * and to dobinding.cpp. It should be hidden from javabind.h
64 *
65 * This file is mostly about getting things up and running, and
66 * providing the basic C-to-Java hooks.
67 *
68 * dobinding.cpp will have the rote and repetitious
69 * class-by-class binding
70 */
73 namespace Inkscape
74 {
76 namespace Bind
77 {
80 //########################################################################
81 //# DEFINITIONS
82 //########################################################################
84 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
88 //########################################################################
89 //# UTILITY
90 //########################################################################
92 /**
93 * Normalize path. Java wants '/', even on Windows
94 */
95 String normalizePath(const String &str)
96 {
97 String buf;
98 for (unsigned int i=0 ; i<str.size() ; i++)
99 {
100 char ch = str[i];
101 if (ch == '\\')
102 buf.push_back('/');
103 else
104 buf.push_back(ch);
105 }
106 return buf;
107 }
110 /**
111 * Convert a java string to a C++ string
112 */
113 String getString(JNIEnv *env, jstring jstr)
114 {
115 const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
116 String str = chars;
117 env->ReleaseStringUTFChars(jstr, chars);
118 return str;
119 }
122 /**
123 * Check if the VM has encountered an Exception. If so, get the String for it
124 * and clear the exception
125 */
126 String getExceptionString(JNIEnv *env)
127 {
128 String buf;
129 jthrowable exc = env->ExceptionOccurred();
130 if (!exc)
131 return buf;
132 jclass cls = env->GetObjectClass(exc);
133 jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
134 jstring jstr = (jstring) env->CallObjectMethod(exc, mid);
135 buf.append(getString(env, jstr));
136 env->ExceptionClear();
137 return buf;
138 }
140 jint getObjInt(JNIEnv *env, jobject obj, const char *name)
141 {
142 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
143 return env->GetIntField(obj, fid);
144 }
146 void setObjInt(JNIEnv *env, jobject obj, const char *name, jint val)
147 {
148 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
149 env->SetIntField(obj, fid, val);
150 }
152 jlong getObjLong(JNIEnv *env, jobject obj, const char *name)
153 {
154 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
155 return env->GetLongField(obj, fid);
156 }
158 void setObjLong(JNIEnv *env, jobject obj, const char *name, jlong val)
159 {
160 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
161 env->SetLongField(obj, fid, val);
162 }
164 jfloat getObjFloat(JNIEnv *env, jobject obj, const char *name)
165 {
166 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
167 return env->GetFloatField(obj, fid);
168 }
170 void setObjFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
171 {
172 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
173 env->SetFloatField(obj, fid, val);
174 }
176 jdouble getObjDouble(JNIEnv *env, jobject obj, const char *name)
177 {
178 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
179 return env->GetDoubleField(obj, fid);
180 }
182 void setObjDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
183 {
184 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
185 env->SetDoubleField(obj, fid, val);
186 }
188 String getObjString(JNIEnv *env, jobject obj, const char *name)
189 {
190 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
191 jstring jstr = (jstring)env->GetObjectField(obj, fid);
192 return getString(env, jstr);
193 }
195 void setObjString(JNIEnv *env, jobject obj, const char *name, const String &val)
196 {
197 jstring jstr = env->NewStringUTF(val.c_str());
198 jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
199 env->SetObjectField(obj, fid, jstr);
200 }
205 //########################################################################
206 //# CONSTRUCTOR/DESTRUCTOR
207 //########################################################################
209 static JavaBinderyImpl *_instance = NULL;
211 JavaBindery *JavaBindery::getInstance()
212 {
213 return JavaBinderyImpl::getInstance();
214 }
216 JavaBinderyImpl *JavaBinderyImpl::getInstance()
217 {
218 if (!_instance)
219 {
220 _instance = new JavaBinderyImpl();
221 }
222 return _instance;
223 }
225 JavaBinderyImpl::JavaBinderyImpl()
226 {
227 jvm = NULL;
228 env = NULL;
229 gatewayObj = NULL;
230 }
232 JavaBinderyImpl::~JavaBinderyImpl()
233 {
234 }
237 //########################################################################
238 //# MESSAGES
239 //########################################################################
241 void err(const char *fmt, ...)
242 {
243 #if 0
244 va_list args;
245 fprintf(stderr, "JavaBinderyImpl err:");
246 va_start(args, fmt);
247 vfprintf(stderr, fmt, args);
248 va_end(args);
249 fprintf(stderr, "\n");
250 #else
251 va_list args;
252 g_warning("JavaBinderyImpl err:");
253 va_start(args, fmt);
254 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
255 va_end(args);
256 g_warning("\n");
257 #endif
258 }
260 void msg(const char *fmt, ...)
261 {
262 #if 0
263 va_list args;
264 fprintf(stdout, "JavaBinderyImpl:");
265 va_start(args, fmt);
266 vfprintf(stdout, fmt, args);
267 va_end(args);
268 fprintf(stdout, "\n");
269 #else
270 va_list args;
271 g_message("JavaBinderyImpl:");
272 va_start(args, fmt);
273 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
274 va_end(args);
275 g_message("\n");
276 #endif
277 }
281 //########################################################################
282 //# W I N 3 2 S T Y L E
283 //########################################################################
284 #ifdef __WIN32__
287 #define DIR_SEPARATOR "\\"
288 #define PATH_SEPARATOR ";"
292 static bool getRegistryString(HKEY /*root*/, const char *keyName,
293 const char *valName, char *buf, int buflen)
294 {
295 HKEY key;
296 DWORD bufsiz = buflen;
297 RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
298 int ret = RegQueryValueEx(key, TEXT(valName),
299 NULL, NULL, (BYTE *)buf, &bufsiz);
300 if (ret != ERROR_SUCCESS)
301 {
302 err("Key '%s\\%s not found\n", keyName, valName);
303 return false;
304 }
305 RegCloseKey(key);
306 return true;
307 }
310 static String cleanPath(const String &s)
311 {
312 String buf;
313 for (unsigned int i=0 ; i<s.size() ; i++)
314 {
315 char ch = s[i];
316 if (ch != '"')
317 buf.push_back(ch);
318 }
319 return buf;
320 }
323 /**
324 * Common places to find jvm.dll under JAVA_HOME
325 */
326 static const char *commonJavaPaths[] =
327 {
328 "\\jre\\bin\\client\\jvm.dll",
329 "\\bin\\client\\jvm.dll",
330 "\\jvm.dll",
331 NULL
332 };
335 /**
336 * Return the directory of the .exe that is currently running
337 */
338 static String getExePath()
339 {
340 char exeName[MAX_PATH+1];
341 GetModuleFileName(NULL, exeName, MAX_PATH);
342 char *slashPos = strrchr(exeName, '\\');
343 if (slashPos)
344 *slashPos = '\0';
345 String s = exeName;
346 return s;
347 }
350 /**
351 * Check a directory for several possibilities of sub-locations
352 * under it, where a jvm might exist.
353 */
354 static String checkPathUnderRoot(const String &root)
355 {
356 for (const char **path = commonJavaPaths ; *path ; path++)
357 {
358 String jpath = root;
359 jpath.append(*path);
360 //msg("trying '%s'", jpath.c_str());
361 struct stat finfo;
362 if (stat(jpath.c_str(), &finfo)>=0)
363 {
364 //msg("found");
365 return jpath;
366 }
367 }
368 return "";
369 }
373 /**
374 * Attempt to find and load a jvm.dll file. Find the createVM()
375 * function's address and return it
376 */
377 static CreateVMFunc getCreateVMFunc()
378 {
379 bool found = false;
380 String libname;
382 /**
383 * First, look for an embedded jre in the .exe's dir.
384 * This allows us to package our own JRE if we want to.
385 */
386 String inkscapeHome = getExePath();
387 inkscapeHome.append("\\jre");
388 msg("INKSCAPE_HOME='%s'", inkscapeHome.c_str());
389 String path = checkPathUnderRoot(inkscapeHome);
390 if (path.size() > 0)
391 {
392 libname = path;
393 found = true;
394 }
396 /**
397 * Next, look for JAVA_HOME. This will allow the user
398 * to override what's in the registry
399 */
400 if (!found)
401 {
402 const char *envStr = getenv("JAVA_HOME");
403 if (envStr)
404 {
405 String javaHome = cleanPath(envStr);
406 msg("JAVA_HOME='%s'", javaHome.c_str());
407 path = checkPathUnderRoot(javaHome);
408 if (path.size() > 0)
409 {
410 libname = path;
411 found = true;
412 }
413 }
414 }
416 //not at JAVA_HOME. check the registry
417 if (!found)
418 {
419 char verbuf[16];
420 char regpath[80];
421 strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
422 bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
423 regpath, "CurrentVersion", verbuf, 15);
424 if (!ret)
425 {
426 msg("JVM CurrentVersion not found in registry at '%s'", regpath);
427 }
428 else
429 {
430 strcat(regpath, "\\");
431 strcat(regpath, verbuf);
432 //msg("reg path: %s\n", regpath);
433 char valbuf[80];
434 ret = getRegistryString(HKEY_LOCAL_MACHINE,
435 regpath, "RuntimeLib", valbuf, 79);
436 if (ret)
437 {
438 found = true;
439 libname = valbuf;
440 }
441 else
442 {
443 msg("JVM RuntimeLib not found in registry at '%s'",
444 regpath);
445 }
446 }
447 }
449 if (!found)
450 {
451 err("JVM not found at JAVA_HOME or in registry");
452 return NULL;
453 }
455 /**
456 * If we are here, then we seem to have a valid path for jvm.dll
457 * Give it a try
458 */
459 msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
460 HMODULE lib = LoadLibrary(libname.c_str());
461 if (!lib)
462 {
463 err("Java VM not found at '%s'", libname.c_str());
464 return NULL;
465 }
466 CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
467 if (!createVM)
468 {
469 err("Could not find 'JNI_CreateJavaVM' in shared library '%s'",
470 libname.c_str());
471 return NULL;
472 }
473 return createVM;
474 }
476 /**
477 * Return the directory where the Java classes/libs/resources are
478 * located
479 */
480 static void getJavaRoot(String &javaroot)
481 {
482 javaroot = getExePath();
483 javaroot.append("\\");
484 javaroot.append(INKSCAPE_BINDDIR);
485 javaroot.append("\\java");
486 }
491 //########################################################################
492 //# U N I X S T Y L E
493 //########################################################################
494 #else /* !__WIN32__ */
497 #define DIR_SEPARATOR "/"
498 #define PATH_SEPARATOR ":"
501 /**
502 * Recursively descend into a directory looking for libjvm.so
503 */
504 static bool findJVMRecursive(const String &dirpath,
505 std::vector<String> &results)
506 {
507 DIR *dir = opendir(dirpath.c_str());
508 if (!dir)
509 return false;
510 bool ret = false;
511 while (true)
512 {
513 struct dirent *de = readdir(dir);
514 if (!de)
515 break;
516 String fname = de->d_name;
517 if (fname == "." || fname == "..")
518 continue;
519 String path = dirpath;
520 path.push_back('/');
521 path.append(fname);
522 if (fname == "libjvm.so")
523 {
524 ret = true;
525 results.push_back(path);
526 continue;
527 }
528 struct stat finfo;
529 if (lstat(path.c_str(), &finfo)<0)
530 {
531 break;
532 }
533 if (finfo.st_mode & S_IFDIR)
534 {
535 ret |= findJVMRecursive(path, results);
536 }
537 }
538 closedir(dir);
539 return ret;
540 }
543 /**
544 * Some common places on a Unix filesystem where JVMs are
545 * often found.
546 */
547 static const char *commonJavaPaths[] =
548 {
549 "/usr/lib/jvm/jre",
550 "/usr/lib/jvm",
551 "/usr/local/lib/jvm/jre",
552 "/usr/local/lib/jvm",
553 "/usr/java",
554 "/usr/local/java",
555 NULL
556 };
560 /**
561 * Look for a Java VM (libjvm.so) in several Unix places
562 */
563 static bool findJVM(String &result)
564 {
565 std::vector<String> results;
566 int found = false;
568 /* Is there one specified by the user? */
569 const char *javaHome = getenv("JAVA_HOME");
570 if (javaHome && findJVMRecursive(javaHome, results))
571 found = true;
572 else for (const char **path = commonJavaPaths ; *path ; path++)
573 {
574 if (findJVMRecursive(*path, results))
575 {
576 found = true;
577 break;
578 }
579 }
580 if (!found)
581 {
582 return false;
583 }
584 if (results.size() == 0)
585 return false;
586 //Look first for a Client VM
587 for (unsigned int i=0 ; i<results.size() ; i++)
588 {
589 String s = results[i];
590 if (s.find("client") != s.npos)
591 {
592 result = s;
593 return true;
594 }
595 }
596 //else default to the first
597 result = results[0];
598 return true;
599 }
603 /**
604 * Attempt to find and load a jvm.dll file. Find the createVM()
605 * function's address and return it
606 */
607 static CreateVMFunc getCreateVMFunc()
608 {
609 String libname;
610 if (!findJVM(libname))
611 {
612 err("No Java VM found. Is JAVA_HOME defined? Need to find 'libjvm.so'");
613 return NULL;
614 }
615 msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
616 void *lib = dlopen(libname.c_str(), RTLD_NOW);
617 if (!lib)
618 {
619 err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
620 return NULL;
621 }
622 CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
623 if (!createVM)
624 {
625 err("Could not find 'JNI_CreateJavaVM' in shared library");
626 return NULL;
627 }
628 return createVM;
629 }
632 /**
633 * Return the directory where the Java classes/libs/resources are
634 * located
635 */
636 static void getJavaRoot(String &javaroot)
637 {
638 javaroot = INKSCAPE_BINDDIR;
639 javaroot.append("/java");
640 }
642 #endif /* !__WIN32__ */
645 //########################################################################
646 //# COMMON
647 //########################################################################
650 bool JavaBinderyImpl::isLoaded()
651 {
652 return (jvm != (void *)0);
653 }
657 /**
658 * This will set up the classpath for the launched VM.
659 * We will add two things:
660 * 1. INKSCAPE_JAVADIR/classes -- path to loose classes
661 * 2. A concatenation of all jar files in INKSCAPE_JAVADIR/lib
662 *
663 * This will allow people to add classes and jars to the JVM without
664 * needing to state them explicitly.
665 *
666 * @param javaroot. Should be INKSCAPE_JAVADIR
667 * @param result a string buffer to hold the result of this method
668 */
669 static void populateClassPath(const String &javaroot,
670 String &result)
671 {
672 String classdir = javaroot;
673 classdir.append(DIR_SEPARATOR);
674 classdir.append("classes");
676 String cp = classdir;
678 String libdir = javaroot;
679 libdir.append(DIR_SEPARATOR);
680 libdir.append("lib");
682 DIR *dir = opendir(libdir.c_str());
683 if (!dir)
684 {
685 result = cp;
686 return;
687 }
689 while (true)
690 {
691 struct dirent *de = readdir(dir);
692 if (!de)
693 break;
694 String fname = de->d_name;
695 if (fname == "." || fname == "..")
696 continue;
697 if (fname.size()<5) //x.jar
698 continue;
699 if (fname.compare(fname.size()-4, 4, ".jar") != 0)
700 continue;
702 String path = libdir;
703 path.append(DIR_SEPARATOR);
704 path.append(fname);
706 cp.append(PATH_SEPARATOR);
707 cp.append(path);
708 }
709 closedir(dir);
711 result = cp;
712 }
716 //========================================================================
717 // Gateway
718 //========================================================================
719 /**
720 * This is provided to scripts can grab the current copy or the
721 * repr tree. If anyone has a smarter way of doing this, please implement.
722 */
723 jstring JNICALL documentGet(JNIEnv *env, jobject /*obj*/, jlong /*ptr*/)
724 {
725 //JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
726 String buf = sp_repr_save_buf((SP_ACTIVE_DOCUMENT)->rdoc);
727 jstring jstr = env->NewStringUTF(buf.c_str());
728 return jstr;
729 }
731 /**
732 * This is provided to scripts can load an XML tree into Inkscape.
733 * If anyone has a smarter way of doing this, please implement.
734 */
735 jboolean JNICALL documentSet(JNIEnv *env, jobject /*obj*/, jlong /*ptr*/, jstring jstr)
736 {
737 //JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
738 String s = getString(env, jstr);
739 SPDocument *doc = sp_document_new_from_mem(s.c_str(), s.size(), true);
740 }
742 /**
743 * This method is used to allow the gateway class to
744 * redirect its logging stream here.
745 * For the main C++/Java bindings, see dobinding.cpp
746 */
747 void JNICALL logWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
748 {
749 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
750 bind->log(ch);
751 }
754 static JNINativeMethod gatewayMethods[] =
755 {
756 { (char *)"documentGet", (char *)"(J)Ljava/lang/String;", (void *)documentGet },
757 { (char *)"documentSet", (char *)"(JLjava/lang/String;)Z", (void *)documentSet },
758 { (char *)"logWrite", (char *)"(JI)V", (void *)logWrite },
759 { NULL, NULL, NULL }
760 };
763 /**
764 * This sets up the 'Gateway' java class for execution of
765 * scripts. The class's constructor takes a jlong. This java long
766 * is used to store the pointer to 'this'. When ScriptRunner makes
767 * native calls, it passes that jlong back, so that it can call the
768 * methods of this C++ class.
769 */
770 bool JavaBinderyImpl::setupGateway()
771 {
772 String className = "org/inkscape/cmn/Gateway";
773 if (!registerNatives(className, gatewayMethods))
774 {
775 return false;
776 }
777 jclass cls = env->FindClass(className.c_str());
778 if (!cls)
779 {
780 err("setupGateway: cannot find class '%s' : %s",
781 className.c_str(), getException().c_str());
782 return false;
783 }
784 jmethodID mid = env->GetMethodID(cls, "<init>", "(J)V");
785 if (!mid)
786 {
787 err("setupGateway: cannot find constructor for '%s' : %s",
788 className.c_str(), getException().c_str());
789 return false;
790 }
791 gatewayObj = env->NewObject(cls, mid, ((jlong)this));
792 if (!gatewayObj)
793 {
794 err("setupGateway: cannot construct '%s' : %s",
795 className.c_str(), getException().c_str());
796 return false;
797 }
799 msg("Gateway ready");
800 return true;
801 }
803 bool JavaBinderyImpl::scriptRun(const String &lang, const String &script)
804 {
805 if (!loadJVM())
806 return false;
808 std::vector<Value> params;
809 Value langParm(lang);
810 params.push_back(langParm);
811 Value scriptParm(script);
812 params.push_back(scriptParm);
813 Value retval;
814 callInstance(Value::BIND_VOID, gatewayObj, "scriptRun",
815 "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
816 return retval.getBoolean();
817 }
819 bool JavaBinderyImpl::scriptRunFile(const String &lang, const String &fname)
820 {
821 if (!loadJVM())
822 return false;
824 std::vector<Value> params;
825 Value langParm(lang);
826 params.push_back(langParm);
827 Value fnameParm(fname);
828 params.push_back(fnameParm);
829 Value retval;
830 callInstance(Value::BIND_VOID, gatewayObj, "scriptRunFile",
831 "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
832 return retval.getBoolean();
833 }
835 bool JavaBinderyImpl::showConsole()
836 {
837 if (!loadJVM())
838 return false;
840 std::vector<Value> params;
841 Value retval;
842 callInstance(Value::BIND_VOID, gatewayObj, "showConsole",
843 "()Z", params, retval);
844 return retval.getBoolean();
845 }
848 //========================================================================
849 // End Gateway
850 //========================================================================
853 /**
854 * This is used to grab output from the VM itself. See 'options' below.
855 */
856 static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args)
857 {
858 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
859 }
862 /**
863 * This is the most important part of this class. Here we
864 * attempt to find, load, and initialize a java (or mlvm?) virtual
865 * machine.
866 *
867 * @return true if successful, else false
868 */
869 bool JavaBinderyImpl::loadJVM()
870 {
871 if (jvm)
872 return true;
874 CreateVMFunc createVM = getCreateVMFunc();
875 if (!createVM)
876 {
877 err("Could not find 'JNI_CreateJavaVM' in shared library");
878 return false;
879 }
881 String javaroot;
882 getJavaRoot(javaroot);
883 String cp;
884 populateClassPath(javaroot, cp);
885 String classpath = "-Djava.class.path=";
886 classpath.append(normalizePath(cp));
887 msg("Class path is: '%s'", classpath.c_str());
889 String libpath = "-Djava.library.path=";
890 libpath.append(javaroot);
891 libpath.append(DIR_SEPARATOR);
892 libpath.append("libm");
893 libpath = normalizePath(libpath);
894 msg("Lib path is: '%s'", libpath.c_str());
896 JavaVMInitArgs vm_args;
897 JavaVMOption options[10];//should be enough
898 int nOptions = 0;
899 options[nOptions++].optionString = (char *)classpath.c_str();
900 options[nOptions++].optionString = (char *)libpath.c_str();
901 //options[nOptions++].optionString = (char *)"-verbose:jni";
902 options[nOptions ].optionString = (char *)"vfprintf";
903 options[nOptions++].extraInfo = (void *)vfprintfHook;
904 vm_args.version = JNI_VERSION_1_4;
905 vm_args.options = options;
906 vm_args.nOptions = nOptions;
907 vm_args.ignoreUnrecognized = true;
909 if (createVM(&jvm, &env, &vm_args) < 0)
910 {
911 err("JNI_CreateJavaVM() failed");
912 return false;
913 }
915 //get jvm version
916 jint vers = env->GetVersion();
917 int versionMajor = (vers>>16) & 0xffff;
918 int versionMinor = (vers ) & 0xffff;
919 msg("Loaded JVM version %d.%d", versionMajor, versionMinor);
921 if (!setupGateway())
922 return false;
924 return true;
925 }
928 /**
929 * This is a difficult method. What we are doing is trying to
930 * call a static method with a list of arguments. Similar to
931 * a varargs call, we need to marshal the Values into their
932 * Java equivalents and make the proper call.
933 *
934 * @param type the return type of the method
935 * @param className the full (package / name) name of the java class
936 * @param methodName the name of the method being invoked
937 * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
938 * that describes the param and return types of the method.
939 * @param retval the return value of the java method
940 * @return true if the call was successful, else false. This is not
941 * the return value of the method.
942 */
943 bool JavaBinderyImpl::callStatic(int type,
944 const String &className,
945 const String &methodName,
946 const String &signature,
947 const std::vector<Value> ¶ms,
948 Value &retval)
949 {
950 jclass cls = env->FindClass(className.c_str());
951 if (!cls)
952 {
953 err("Could not find class '%s' : %s",
954 className.c_str(), getException().c_str());
955 return false;
956 }
957 jmethodID mid = env->GetStaticMethodID(cls,
958 methodName.c_str(), signature.c_str());
959 if (!mid)
960 {
961 err("Could not find method '%s:%s/%s' : %s",
962 className.c_str(), methodName.c_str(),
963 signature.c_str(), getException().c_str());
964 return false;
965 }
966 /**
967 * Assemble your parameters into a form usable by JNI
968 */
969 jvalue *jvals = new jvalue[params.size()];
970 for (unsigned int i=0 ; i<params.size() ; i++)
971 {
972 Value v = params[i];
973 switch (v.getType())
974 {
975 case Value::BIND_BOOLEAN:
976 {
977 jvals[i].z = (jboolean)v.getBoolean();
978 break;
979 }
980 case Value::BIND_INT:
981 {
982 jvals[i].i = (jint)v.getInt();
983 break;
984 }
985 case Value::BIND_DOUBLE:
986 {
987 jvals[i].d = (jdouble)v.getDouble();
988 break;
989 }
990 case Value::BIND_STRING:
991 {
992 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
993 break;
994 }
995 default:
996 {
997 err("Unknown value type: %d", v.getType());
998 return false;
999 }
1000 }
1001 }
1002 switch (type)
1003 {
1004 case Value::BIND_VOID:
1005 {
1006 env->CallStaticVoidMethodA(cls, mid, jvals);
1007 break;
1008 }
1009 case Value::BIND_BOOLEAN:
1010 {
1011 jboolean ret = env->CallStaticBooleanMethodA(cls, mid, jvals);
1012 if (ret == JNI_TRUE) //remember, don't truncate
1013 retval.setBoolean(true);
1014 else
1015 retval.setBoolean(false);
1016 break;
1017 }
1018 case Value::BIND_INT:
1019 {
1020 jint ret = env->CallStaticIntMethodA(cls, mid, jvals);
1021 retval.setInt(ret);
1022 break;
1023 }
1024 case Value::BIND_DOUBLE:
1025 {
1026 jdouble ret = env->CallStaticDoubleMethodA(cls, mid, jvals);
1027 retval.setDouble(ret);
1028 break;
1029 }
1030 case Value::BIND_STRING:
1031 {
1032 jobject ret = env->CallStaticObjectMethodA(cls, mid, jvals);
1033 jstring jstr = (jstring) ret;
1034 const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1035 retval.setString(str);
1036 env->ReleaseStringUTFChars(jstr, str);
1037 break;
1038 }
1039 default:
1040 {
1041 err("Unknown return type: %d", type);
1042 return false;
1043 }
1044 }
1045 delete jvals;
1046 String errStr = getException();
1047 if (errStr.size()>0)
1048 {
1049 err("callStatic: %s", errStr.c_str());
1050 return false;
1051 }
1052 return true;
1053 }
1057 /**
1058 * Another difficult method. However, this time we are operating
1059 * on an existing instance jobject.
1060 *
1061 * @param type the return type of the method
1062 * @param obj the instance upon which to make the call
1063 * @param methodName the name of the method being invoked
1064 * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
1065 * that describes the param and return types of the method.
1066 * @param retval the return value of the java method
1067 * @return true if the call was successful, else false. This is not
1068 * the return value of the method.
1069 */
1070 bool JavaBinderyImpl::callInstance(
1071 int type,
1072 const jobject obj,
1073 const String &methodName,
1074 const String &signature,
1075 const std::vector<Value> ¶ms,
1076 Value &retval)
1077 {
1078 jmethodID mid = env->GetMethodID(env->GetObjectClass(obj),
1079 methodName.c_str(), signature.c_str());
1080 if (!mid)
1081 {
1082 err("Could not find method '%s/%s' : %s",
1083 methodName.c_str(),
1084 signature.c_str(), getException().c_str());
1085 return false;
1086 }
1087 /**
1088 * Assemble your parameters into a form usable by JNI
1089 */
1090 jvalue *jvals = new jvalue[params.size()];
1091 for (unsigned int i=0 ; i<params.size() ; i++)
1092 {
1093 Value v = params[i];
1094 switch (v.getType())
1095 {
1096 case Value::BIND_BOOLEAN:
1097 {
1098 jvals[i].z = (jboolean)v.getBoolean();
1099 break;
1100 }
1101 case Value::BIND_INT:
1102 {
1103 jvals[i].i = (jint)v.getInt();
1104 break;
1105 }
1106 case Value::BIND_DOUBLE:
1107 {
1108 jvals[i].d = (jdouble)v.getDouble();
1109 break;
1110 }
1111 case Value::BIND_STRING:
1112 {
1113 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
1114 break;
1115 }
1116 default:
1117 {
1118 err("Unknown value type: %d", v.getType());
1119 return false;
1120 }
1121 }
1122 }
1123 switch (type)
1124 {
1125 case Value::BIND_VOID:
1126 {
1127 env->CallVoidMethodA(obj, mid, jvals);
1128 break;
1129 }
1130 case Value::BIND_BOOLEAN:
1131 {
1132 jboolean ret = env->CallBooleanMethodA(obj, mid, jvals);
1133 if (ret == JNI_TRUE) //remember, don't truncate
1134 retval.setBoolean(true);
1135 else
1136 retval.setBoolean(false);
1137 break;
1138 }
1139 case Value::BIND_INT:
1140 {
1141 jint ret = env->CallIntMethodA(obj, mid, jvals);
1142 retval.setInt(ret);
1143 break;
1144 }
1145 case Value::BIND_DOUBLE:
1146 {
1147 jdouble ret = env->CallDoubleMethodA(obj, mid, jvals);
1148 retval.setDouble(ret);
1149 break;
1150 }
1151 case Value::BIND_STRING:
1152 {
1153 jobject ret = env->CallObjectMethodA(obj, mid, jvals);
1154 jstring jstr = (jstring) ret;
1155 const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1156 retval.setString(str);
1157 env->ReleaseStringUTFChars(jstr, str);
1158 break;
1159 }
1160 default:
1161 {
1162 err("Unknown return type: %d", type);
1163 return false;
1164 }
1165 }
1166 delete jvals;
1167 String errStr = getException();
1168 if (errStr.size()>0)
1169 {
1170 err("callStatic: %s", errStr.c_str());
1171 return false;
1172 }
1173 return true;
1174 }
1179 /**
1180 * Fetch the last exception from the JVM, if any. Clear it to
1181 * continue processing
1182 *
1183 * @return the exception's descriptio,if any. Else ""
1184 */
1185 String JavaBinderyImpl::getException()
1186 {
1187 return getExceptionString(env);
1188 }
1192 /**
1193 * Convenience method to call the static void main(String argv[])
1194 * method of a given class
1195 *
1196 * @param className full name of the java class
1197 * @args the argument strings to the method
1198 * @return true if successful, else false
1199 */
1200 bool JavaBinderyImpl::callMain(const String &className,
1201 const std::vector<String> &args)
1202 {
1203 std::vector<Value> parms;
1204 for (unsigned int i=0 ; i<args.size() ; i++)
1205 {
1206 Value v;
1207 v.setString(args[i]);
1208 parms.push_back(v);
1209 }
1210 Value retval;
1211 return callStatic(Value::BIND_VOID, className, "main",
1212 "([Ljava/lang/String;)V", parms, retval);
1213 }
1216 /**
1217 * Used to register an array of native methods for a named class
1218 *
1219 * @param className the full name of the java class
1220 * @param the method array
1221 * @return true if successful, else false
1222 */
1223 bool JavaBinderyImpl::registerNatives(const String &className,
1224 const JNINativeMethod *methods)
1225 {
1226 jclass cls = env->FindClass(className.c_str());
1227 if (!cls)
1228 {
1229 err("Could not find class '%s'", className.c_str());
1230 return false;
1231 }
1232 //msg("registerNatives: class '%s' found", className.c_str());
1234 /**
1235 * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
1236 */
1237 jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
1238 "()[Ljava/lang/reflect/Constructor;");
1239 if (!mid)
1240 {
1241 err("Could not get reflect mid for 'getConstructors' : %s",
1242 getException().c_str());
1243 return false;
1244 }
1245 jobject res = env->CallObjectMethod(cls, mid);
1246 if (!res)
1247 {
1248 err("Could not get constructors : %s", getException().c_str());
1249 return false;
1250 }
1251 /**
1252 * end hack
1253 */
1254 jint nrMethods = 0;
1255 for (const JNINativeMethod *m = methods ; m->name ; m++)
1256 nrMethods++;
1257 jint ret = env->RegisterNatives(cls, methods, nrMethods);
1258 if (ret < 0)
1259 {
1260 err("Could not register %d native methods for '%s' : %s",
1261 nrMethods, className.c_str(), getException().c_str());
1262 return false;
1263 }
1264 return true;
1265 }
1270 } // namespace Bind
1271 } // namespace Inkscape
1273 //########################################################################
1274 //# E N D O F F I L E
1275 //########################################################################