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 /*
483 javaroot = getExePath();
484 javaroot.append("\\");
485 javaroot.append(INKSCAPE_BINDDIR);
486 javaroot.append("\\java");
487 */
488 javaroot = INKSCAPE_BINDDIR;
489 javaroot.append("\\java");
490 }
495 //########################################################################
496 //# U N I X S T Y L E
497 //########################################################################
498 #else /* !__WIN32__ */
501 #define DIR_SEPARATOR "/"
502 #define PATH_SEPARATOR ":"
505 /**
506 * Recursively descend into a directory looking for libjvm.so
507 */
508 static bool findJVMRecursive(const String &dirpath,
509 std::vector<String> &results)
510 {
511 DIR *dir = opendir(dirpath.c_str());
512 if (!dir)
513 return false;
514 bool ret = false;
515 while (true)
516 {
517 struct dirent *de = readdir(dir);
518 if (!de)
519 break;
520 String fname = de->d_name;
521 if (fname == "." || fname == "..")
522 continue;
523 String path = dirpath;
524 path.push_back('/');
525 path.append(fname);
526 if (fname == "libjvm.so")
527 {
528 ret = true;
529 results.push_back(path);
530 continue;
531 }
532 struct stat finfo;
533 if (lstat(path.c_str(), &finfo)<0)
534 {
535 break;
536 }
537 if (finfo.st_mode & S_IFDIR)
538 {
539 ret |= findJVMRecursive(path, results);
540 }
541 }
542 closedir(dir);
543 return ret;
544 }
547 /**
548 * Some common places on a Unix filesystem where JVMs are
549 * often found.
550 */
551 static const char *commonJavaPaths[] =
552 {
553 "/usr/lib/jvm/jre",
554 "/usr/lib/jvm",
555 "/usr/local/lib/jvm/jre",
556 "/usr/local/lib/jvm",
557 "/usr/java",
558 "/usr/local/java",
559 NULL
560 };
564 /**
565 * Look for a Java VM (libjvm.so) in several Unix places
566 */
567 static bool findJVM(String &result)
568 {
569 std::vector<String> results;
570 int found = false;
572 /* Is there one specified by the user? */
573 const char *javaHome = getenv("JAVA_HOME");
574 if (javaHome && findJVMRecursive(javaHome, results))
575 found = true;
576 else for (const char **path = commonJavaPaths ; *path ; path++)
577 {
578 if (findJVMRecursive(*path, results))
579 {
580 found = true;
581 break;
582 }
583 }
584 if (!found)
585 {
586 return false;
587 }
588 if (results.size() == 0)
589 return false;
590 //Look first for a Client VM
591 for (unsigned int i=0 ; i<results.size() ; i++)
592 {
593 String s = results[i];
594 if (s.find("client") != s.npos)
595 {
596 result = s;
597 return true;
598 }
599 }
600 //else default to the first
601 result = results[0];
602 return true;
603 }
607 /**
608 * Attempt to find and load a jvm.dll file. Find the createVM()
609 * function's address and return it
610 */
611 static CreateVMFunc getCreateVMFunc()
612 {
613 String libname;
614 if (!findJVM(libname))
615 {
616 err("No Java VM found. Is JAVA_HOME defined? Need to find 'libjvm.so'");
617 return NULL;
618 }
619 msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
620 void *lib = dlopen(libname.c_str(), RTLD_NOW);
621 if (!lib)
622 {
623 err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
624 return NULL;
625 }
626 CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
627 if (!createVM)
628 {
629 err("Could not find 'JNI_CreateJavaVM' in shared library");
630 return NULL;
631 }
632 return createVM;
633 }
636 /**
637 * Return the directory where the Java classes/libs/resources are
638 * located
639 */
640 static void getJavaRoot(String &javaroot)
641 {
642 javaroot = INKSCAPE_BINDDIR;
643 javaroot.append("/java");
644 }
646 #endif /* !__WIN32__ */
649 //########################################################################
650 //# COMMON
651 //########################################################################
654 bool JavaBinderyImpl::isLoaded()
655 {
656 return (jvm != (void *)0);
657 }
661 /**
662 * This will set up the classpath for the launched VM.
663 * We will add two things:
664 * 1. INKSCAPE_JAVADIR/classes -- path to loose classes
665 * 2. A concatenation of all jar files in INKSCAPE_JAVADIR/lib
666 *
667 * This will allow people to add classes and jars to the JVM without
668 * needing to state them explicitly.
669 *
670 * @param javaroot. Should be INKSCAPE_JAVADIR
671 * @param result a string buffer to hold the result of this method
672 */
673 static void populateClassPath(const String &javaroot,
674 String &result)
675 {
676 String classdir = javaroot;
677 classdir.append(DIR_SEPARATOR);
678 classdir.append("classes");
680 String cp = classdir;
682 String libdir = javaroot;
683 libdir.append(DIR_SEPARATOR);
684 libdir.append("lib");
686 DIR *dir = opendir(libdir.c_str());
687 if (!dir)
688 {
689 result = cp;
690 return;
691 }
693 while (true)
694 {
695 struct dirent *de = readdir(dir);
696 if (!de)
697 break;
698 String fname = de->d_name;
699 if (fname == "." || fname == "..")
700 continue;
701 if (fname.size()<5) //x.jar
702 continue;
703 if (fname.compare(fname.size()-4, 4, ".jar") != 0)
704 continue;
706 String path = libdir;
707 path.append(DIR_SEPARATOR);
708 path.append(fname);
710 cp.append(PATH_SEPARATOR);
711 cp.append(path);
712 }
713 closedir(dir);
715 result = cp;
716 }
720 //========================================================================
721 // Gateway
722 //========================================================================
723 /**
724 * This is provided to scripts can grab the current copy or the
725 * repr tree. If anyone has a smarter way of doing this, please implement.
726 */
727 jstring JNICALL documentGet(JNIEnv *env, jobject /*obj*/, jlong /*ptr*/)
728 {
729 //JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
730 String buf = sp_repr_save_buf((SP_ACTIVE_DOCUMENT)->rdoc);
731 jstring jstr = env->NewStringUTF(buf.c_str());
732 return jstr;
733 }
735 /**
736 * This is provided to scripts can load an XML tree into Inkscape.
737 * If anyone has a smarter way of doing this, please implement.
738 */
739 jboolean JNICALL documentSet(JNIEnv */*env*/, jobject /*obj*/, jlong /*ptr*/, jstring /*jstr*/)
740 {
741 /*
742 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
743 String s = getString(env, jstr);
744 SPDocument *doc = sp_document_new_from_mem(s.c_str(), s.size(), true);
745 */
746 return JNI_TRUE;
747 }
749 /**
750 * This method is used to allow the gateway class to
751 * redirect its logging stream here.
752 * For the main C++/Java bindings, see dobinding.cpp
753 */
754 void JNICALL logWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
755 {
756 JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
757 bind->log(ch);
758 }
761 static JNINativeMethod gatewayMethods[] =
762 {
763 { (char *)"documentGet", (char *)"(J)Ljava/lang/String;", (void *)documentGet },
764 { (char *)"documentSet", (char *)"(JLjava/lang/String;)Z", (void *)documentSet },
765 { (char *)"logWrite", (char *)"(JI)V", (void *)logWrite },
766 { NULL, NULL, NULL }
767 };
770 /**
771 * This sets up the 'Gateway' java class for execution of
772 * scripts. The class's constructor takes a jlong. This java long
773 * is used to store the pointer to 'this'. When ScriptRunner makes
774 * native calls, it passes that jlong back, so that it can call the
775 * methods of this C++ class.
776 */
777 bool JavaBinderyImpl::setupGateway()
778 {
779 String className = "org/inkscape/cmn/Gateway";
780 if (!registerNatives(className, gatewayMethods))
781 {
782 return false;
783 }
784 jclass cls = env->FindClass(className.c_str());
785 if (!cls)
786 {
787 err("setupGateway: cannot find class '%s' : %s",
788 className.c_str(), getException().c_str());
789 return false;
790 }
791 jmethodID mid = env->GetMethodID(cls, "<init>", "(J)V");
792 if (!mid)
793 {
794 err("setupGateway: cannot find constructor for '%s' : %s",
795 className.c_str(), getException().c_str());
796 return false;
797 }
798 gatewayObj = env->NewObject(cls, mid, ((jlong)this));
799 if (!gatewayObj)
800 {
801 err("setupGateway: cannot construct '%s' : %s",
802 className.c_str(), getException().c_str());
803 return false;
804 }
806 msg("Gateway ready");
807 return true;
808 }
810 bool JavaBinderyImpl::scriptRun(const String &lang, const String &script)
811 {
812 if (!loadJVM())
813 return false;
815 std::vector<Value> params;
816 Value langParm(lang);
817 params.push_back(langParm);
818 Value scriptParm(script);
819 params.push_back(scriptParm);
820 Value retval;
821 callInstance(Value::BIND_VOID, gatewayObj, "scriptRun",
822 "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
823 return retval.getBoolean();
824 }
826 bool JavaBinderyImpl::scriptRunFile(const String &lang, const String &fname)
827 {
828 if (!loadJVM())
829 return false;
831 std::vector<Value> params;
832 Value langParm(lang);
833 params.push_back(langParm);
834 Value fnameParm(fname);
835 params.push_back(fnameParm);
836 Value retval;
837 callInstance(Value::BIND_VOID, gatewayObj, "scriptRunFile",
838 "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
839 return retval.getBoolean();
840 }
842 bool JavaBinderyImpl::showConsole()
843 {
844 if (!loadJVM())
845 return false;
847 std::vector<Value> params;
848 Value retval;
849 callInstance(Value::BIND_VOID, gatewayObj, "showConsole",
850 "()Z", params, retval);
851 return retval.getBoolean();
852 }
855 //========================================================================
856 // End Gateway
857 //========================================================================
860 /**
861 * This is used to grab output from the VM itself. See 'options' below.
862 */
863 static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args)
864 {
865 g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
866 return JNI_TRUE;
867 }
870 /**
871 * This is the most important part of this class. Here we
872 * attempt to find, load, and initialize a java (or mlvm?) virtual
873 * machine.
874 *
875 * @return true if successful, else false
876 */
877 bool JavaBinderyImpl::loadJVM()
878 {
879 if (jvm)
880 return true;
882 CreateVMFunc createVM = getCreateVMFunc();
883 if (!createVM)
884 {
885 err("Could not find 'JNI_CreateJavaVM' in shared library");
886 return false;
887 }
889 String javaroot;
890 getJavaRoot(javaroot);
891 String cp;
892 populateClassPath(javaroot, cp);
893 String classpath = "-Djava.class.path=";
894 classpath.append(normalizePath(cp));
895 msg("Class path is: '%s'", classpath.c_str());
897 String libpath = "-Djava.library.path=";
898 libpath.append(javaroot);
899 libpath.append(DIR_SEPARATOR);
900 libpath.append("libm");
901 libpath = normalizePath(libpath);
902 msg("Lib path is: '%s'", libpath.c_str());
904 JavaVMInitArgs vm_args;
905 JavaVMOption options[10];//should be enough
906 int nOptions = 0;
907 options[nOptions++].optionString = (char *)classpath.c_str();
908 options[nOptions++].optionString = (char *)libpath.c_str();
909 //options[nOptions++].optionString = (char *)"-verbose:jni";
910 options[nOptions ].optionString = (char *)"vfprintf";
911 options[nOptions++].extraInfo = (void *)vfprintfHook;
912 vm_args.version = JNI_VERSION_1_4;
913 vm_args.options = options;
914 vm_args.nOptions = nOptions;
915 vm_args.ignoreUnrecognized = true;
917 if (createVM(&jvm, &env, &vm_args) < 0)
918 {
919 err("JNI_CreateJavaVM() failed");
920 return false;
921 }
923 //get jvm version
924 jint vers = env->GetVersion();
925 int versionMajor = (vers>>16) & 0xffff;
926 int versionMinor = (vers ) & 0xffff;
927 msg("Loaded JVM version %d.%d", versionMajor, versionMinor);
929 if (!setupGateway())
930 return false;
932 return true;
933 }
936 /**
937 * This is a difficult method. What we are doing is trying to
938 * call a static method with a list of arguments. Similar to
939 * a varargs call, we need to marshal the Values into their
940 * Java equivalents and make the proper call.
941 *
942 * @param type the return type of the method
943 * @param className the full (package / name) name of the java class
944 * @param methodName the name of the method being invoked
945 * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
946 * that describes the param and return types of the method.
947 * @param retval the return value of the java method
948 * @return true if the call was successful, else false. This is not
949 * the return value of the method.
950 */
951 bool JavaBinderyImpl::callStatic(int type,
952 const String &className,
953 const String &methodName,
954 const String &signature,
955 const std::vector<Value> ¶ms,
956 Value &retval)
957 {
958 jclass cls = env->FindClass(className.c_str());
959 if (!cls)
960 {
961 err("Could not find class '%s' : %s",
962 className.c_str(), getException().c_str());
963 return false;
964 }
965 jmethodID mid = env->GetStaticMethodID(cls,
966 methodName.c_str(), signature.c_str());
967 if (!mid)
968 {
969 err("Could not find method '%s:%s/%s' : %s",
970 className.c_str(), methodName.c_str(),
971 signature.c_str(), getException().c_str());
972 return false;
973 }
974 /**
975 * Assemble your parameters into a form usable by JNI
976 */
977 jvalue *jvals = new jvalue[params.size()];
978 for (unsigned int i=0 ; i<params.size() ; i++)
979 {
980 Value v = params[i];
981 switch (v.getType())
982 {
983 case Value::BIND_BOOLEAN:
984 {
985 jvals[i].z = (jboolean)v.getBoolean();
986 break;
987 }
988 case Value::BIND_INT:
989 {
990 jvals[i].i = (jint)v.getInt();
991 break;
992 }
993 case Value::BIND_DOUBLE:
994 {
995 jvals[i].d = (jdouble)v.getDouble();
996 break;
997 }
998 case Value::BIND_STRING:
999 {
1000 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
1001 break;
1002 }
1003 default:
1004 {
1005 err("Unknown value type: %d", v.getType());
1006 return false;
1007 }
1008 }
1009 }
1010 switch (type)
1011 {
1012 case Value::BIND_VOID:
1013 {
1014 env->CallStaticVoidMethodA(cls, mid, jvals);
1015 break;
1016 }
1017 case Value::BIND_BOOLEAN:
1018 {
1019 jboolean ret = env->CallStaticBooleanMethodA(cls, mid, jvals);
1020 if (ret == JNI_TRUE) //remember, don't truncate
1021 retval.setBoolean(true);
1022 else
1023 retval.setBoolean(false);
1024 break;
1025 }
1026 case Value::BIND_INT:
1027 {
1028 jint ret = env->CallStaticIntMethodA(cls, mid, jvals);
1029 retval.setInt(ret);
1030 break;
1031 }
1032 case Value::BIND_DOUBLE:
1033 {
1034 jdouble ret = env->CallStaticDoubleMethodA(cls, mid, jvals);
1035 retval.setDouble(ret);
1036 break;
1037 }
1038 case Value::BIND_STRING:
1039 {
1040 jobject ret = env->CallStaticObjectMethodA(cls, mid, jvals);
1041 jstring jstr = (jstring) ret;
1042 const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1043 retval.setString(str);
1044 env->ReleaseStringUTFChars(jstr, str);
1045 break;
1046 }
1047 default:
1048 {
1049 err("Unknown return type: %d", type);
1050 return false;
1051 }
1052 }
1053 delete jvals;
1054 String errStr = getException();
1055 if (errStr.size()>0)
1056 {
1057 err("callStatic: %s", errStr.c_str());
1058 return false;
1059 }
1060 return true;
1061 }
1065 /**
1066 * Another difficult method. However, this time we are operating
1067 * on an existing instance jobject.
1068 *
1069 * @param type the return type of the method
1070 * @param obj the instance upon which to make the call
1071 * @param methodName the name of the method being invoked
1072 * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
1073 * that describes the param and return types of the method.
1074 * @param retval the return value of the java method
1075 * @return true if the call was successful, else false. This is not
1076 * the return value of the method.
1077 */
1078 bool JavaBinderyImpl::callInstance(
1079 int type,
1080 const jobject obj,
1081 const String &methodName,
1082 const String &signature,
1083 const std::vector<Value> ¶ms,
1084 Value &retval)
1085 {
1086 jmethodID mid = env->GetMethodID(env->GetObjectClass(obj),
1087 methodName.c_str(), signature.c_str());
1088 if (!mid)
1089 {
1090 err("Could not find method '%s/%s' : %s",
1091 methodName.c_str(),
1092 signature.c_str(), getException().c_str());
1093 return false;
1094 }
1095 /**
1096 * Assemble your parameters into a form usable by JNI
1097 */
1098 jvalue *jvals = new jvalue[params.size()];
1099 for (unsigned int i=0 ; i<params.size() ; i++)
1100 {
1101 Value v = params[i];
1102 switch (v.getType())
1103 {
1104 case Value::BIND_BOOLEAN:
1105 {
1106 jvals[i].z = (jboolean)v.getBoolean();
1107 break;
1108 }
1109 case Value::BIND_INT:
1110 {
1111 jvals[i].i = (jint)v.getInt();
1112 break;
1113 }
1114 case Value::BIND_DOUBLE:
1115 {
1116 jvals[i].d = (jdouble)v.getDouble();
1117 break;
1118 }
1119 case Value::BIND_STRING:
1120 {
1121 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
1122 break;
1123 }
1124 default:
1125 {
1126 err("Unknown value type: %d", v.getType());
1127 return false;
1128 }
1129 }
1130 }
1131 switch (type)
1132 {
1133 case Value::BIND_VOID:
1134 {
1135 env->CallVoidMethodA(obj, mid, jvals);
1136 break;
1137 }
1138 case Value::BIND_BOOLEAN:
1139 {
1140 jboolean ret = env->CallBooleanMethodA(obj, mid, jvals);
1141 if (ret == JNI_TRUE) //remember, don't truncate
1142 retval.setBoolean(true);
1143 else
1144 retval.setBoolean(false);
1145 break;
1146 }
1147 case Value::BIND_INT:
1148 {
1149 jint ret = env->CallIntMethodA(obj, mid, jvals);
1150 retval.setInt(ret);
1151 break;
1152 }
1153 case Value::BIND_DOUBLE:
1154 {
1155 jdouble ret = env->CallDoubleMethodA(obj, mid, jvals);
1156 retval.setDouble(ret);
1157 break;
1158 }
1159 case Value::BIND_STRING:
1160 {
1161 jobject ret = env->CallObjectMethodA(obj, mid, jvals);
1162 jstring jstr = (jstring) ret;
1163 const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1164 retval.setString(str);
1165 env->ReleaseStringUTFChars(jstr, str);
1166 break;
1167 }
1168 default:
1169 {
1170 err("Unknown return type: %d", type);
1171 return false;
1172 }
1173 }
1174 delete jvals;
1175 String errStr = getException();
1176 if (errStr.size()>0)
1177 {
1178 err("callStatic: %s", errStr.c_str());
1179 return false;
1180 }
1181 return true;
1182 }
1187 /**
1188 * Fetch the last exception from the JVM, if any. Clear it to
1189 * continue processing
1190 *
1191 * @return the exception's descriptio,if any. Else ""
1192 */
1193 String JavaBinderyImpl::getException()
1194 {
1195 return getExceptionString(env);
1196 }
1200 /**
1201 * Convenience method to call the static void main(String argv[])
1202 * method of a given class
1203 *
1204 * @param className full name of the java class
1205 * @args the argument strings to the method
1206 * @return true if successful, else false
1207 */
1208 bool JavaBinderyImpl::callMain(const String &className,
1209 const std::vector<String> &args)
1210 {
1211 std::vector<Value> parms;
1212 for (unsigned int i=0 ; i<args.size() ; i++)
1213 {
1214 Value v;
1215 v.setString(args[i]);
1216 parms.push_back(v);
1217 }
1218 Value retval;
1219 return callStatic(Value::BIND_VOID, className, "main",
1220 "([Ljava/lang/String;)V", parms, retval);
1221 }
1224 /**
1225 * Used to register an array of native methods for a named class
1226 *
1227 * @param className the full name of the java class
1228 * @param the method array
1229 * @return true if successful, else false
1230 */
1231 bool JavaBinderyImpl::registerNatives(const String &className,
1232 const JNINativeMethod *methods)
1233 {
1234 jclass cls = env->FindClass(className.c_str());
1235 if (!cls)
1236 {
1237 err("Could not find class '%s'", className.c_str());
1238 return false;
1239 }
1240 //msg("registerNatives: class '%s' found", className.c_str());
1242 /**
1243 * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
1244 */
1245 jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
1246 "()[Ljava/lang/reflect/Constructor;");
1247 if (!mid)
1248 {
1249 err("Could not get reflect mid for 'getConstructors' : %s",
1250 getException().c_str());
1251 return false;
1252 }
1253 jobject res = env->CallObjectMethod(cls, mid);
1254 if (!res)
1255 {
1256 err("Could not get constructors : %s", getException().c_str());
1257 return false;
1258 }
1259 /**
1260 * end hack
1261 */
1262 jint nrMethods = 0;
1263 for (const JNINativeMethod *m = methods ; m->name ; m++)
1264 nrMethods++;
1265 jint ret = env->RegisterNatives(cls, methods, nrMethods);
1266 if (ret < 0)
1267 {
1268 err("Could not register %d native methods for '%s' : %s",
1269 nrMethods, className.c_str(), getException().c_str());
1270 return false;
1271 }
1272 return true;
1273 }
1278 } // namespace Bind
1279 } // namespace Inkscape
1281 //########################################################################
1282 //# E N D O F F I L E
1283 //########################################################################