Code

Connector tool: make connectors avoid the convex hull of shapes.
[inkscape.git] / src / bind / javabind.cpp
1 /**
2  * @file
3  * @brief This is a simple mechanism to bind Inkscape to Java, and thence
4  * to all of the nice things that can be layered upon that.
5  *
6  * Authors:
7  *   Bob Jamison
8  *
9  * Copyright (C) 2007-2008 Bob Jamison
10  *
11  *  This library is free software; you can redistribute it and/or
12  *  modify it under the terms of the GNU Lesser General Public
13  *  License as published by the Free Software Foundation; either
14  *  version 3 of the License, or (at your option) any later version.
15  *
16  *  This library is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  *  Lesser General Public License for more details.
20  *
21  *  You should have received a copy of the GNU Lesser General Public
22  *  License along with this library; if not, write to the Free Software
23  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24  */
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <jni.h>
36 #include <sys/types.h>
37 #include <dirent.h>
40 #ifdef __WIN32__
41 #include <windows.h>
42 #else
43 #include <dlfcn.h>
44 #include <errno.h>
45 #endif
47 #if HAVE_SYS_STAT_H
48 #include <sys/stat.h>
49 #endif
51 #include "javabind.h"
52 #include "javabind-private.h"
53 #include <path-prefix.h>
54 #include <prefix.h>
55 #include <glib/gmessages.h>
57 //For repr and document
58 #include <document.h>
59 #include <inkscape.h>
60 #include <xml/repr.h>
62 /**
63  * Note: We must limit Java or JVM-specific code to this file
64  * and to dobinding.cpp.  It should be hidden from javabind.h
65  * 
66  * This file is mostly about getting things up and running, and
67  * providing the basic C-to-Java hooks.
68  *   
69  * dobinding.cpp will have the rote and repetitious
70  * class-by-class binding   
71  */  
74 namespace Inkscape
75 {
77 namespace Bind
78 {
81 //########################################################################
82 //# DEFINITIONS
83 //########################################################################
85 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
89 //########################################################################
90 //# UTILITY
91 //########################################################################
93 /**
94  * Normalize path.  Java wants '/', even on Windows
95  */ 
96 String normalizePath(const String &str)
97 {
98     String buf;
99     for (unsigned int i=0 ; i<str.size() ; i++)
100         {
101         char ch = str[i];
102         if (ch == '\\')
103             buf.push_back('/');
104         else
105             buf.push_back(ch);
106                 }
107         return buf;
111 /**
112  * Convert a java string to a C++ string
113  */
114 String getString(JNIEnv *env, jstring jstr)
116     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
117     String str = chars;
118     env->ReleaseStringUTFChars(jstr, chars);
119     return str;
123 /**
124  * Check if the VM has encountered an Exception.  If so, get the String for it
125  * and clear the exception
126  */
127 String getExceptionString(JNIEnv *env)
129     String buf;
130     jthrowable exc = env->ExceptionOccurred();
131     if (!exc)
132         return buf;
133     jclass cls = env->GetObjectClass(exc);
134     jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
135     jstring jstr = (jstring) env->CallObjectMethod(exc, mid);
136     buf.append(getString(env, jstr));
137     env->ExceptionClear();
138         return buf;
141 jint getObjInt(JNIEnv *env, jobject obj, const char *name)
143     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
144     return env->GetIntField(obj, fid);
147 void setObjInt(JNIEnv *env, jobject obj, const char *name, jint val)
149     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
150     env->SetIntField(obj, fid, val);
153 jlong getObjLong(JNIEnv *env, jobject obj, const char *name)
155     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
156     return env->GetLongField(obj, fid);
159 void setObjLong(JNIEnv *env, jobject obj, const char *name, jlong val)
161     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
162     env->SetLongField(obj, fid, val);
165 jfloat getObjFloat(JNIEnv *env, jobject obj, const char *name)
167     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
168     return env->GetFloatField(obj, fid);
171 void setObjFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
173     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
174     env->SetFloatField(obj, fid, val);
177 jdouble getObjDouble(JNIEnv *env, jobject obj, const char *name)
179     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
180     return env->GetDoubleField(obj, fid);
183 void setObjDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
185     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
186     env->SetDoubleField(obj, fid, val);
189 String getObjString(JNIEnv *env, jobject obj, const char *name)
191     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
192     jstring jstr = (jstring)env->GetObjectField(obj, fid);
193     return getString(env, jstr);
196 void setObjString(JNIEnv *env, jobject obj, const char *name, const String &val)
198     jstring jstr = env->NewStringUTF(val.c_str());
199     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
200     env->SetObjectField(obj, fid, jstr);
206 //########################################################################
207 //# CONSTRUCTOR/DESTRUCTOR
208 //########################################################################
210 static JavaBinderyImpl *_instance = NULL;
212 JavaBindery *JavaBindery::getInstance()
214     return JavaBinderyImpl::getInstance();
217 JavaBinderyImpl *JavaBinderyImpl::getInstance()
219     if (!_instance)
220         {
221         _instance = new JavaBinderyImpl();
222         }
223     return _instance;
226 JavaBinderyImpl::JavaBinderyImpl()
228     jvm        = NULL;
229     env        = NULL;
230     gatewayObj = NULL;
233 JavaBinderyImpl::~JavaBinderyImpl()
238 //########################################################################
239 //# MESSAGES
240 //########################################################################
242 void err(const char *fmt, ...)
244 #if 0
245     va_list args;
246     fprintf(stderr, "JavaBinderyImpl err:");
247     va_start(args, fmt);
248     vfprintf(stderr, fmt, args);
249     va_end(args);
250     fprintf(stderr, "\n");
251 #else
252     va_list args;
253     g_warning("JavaBinderyImpl err:");
254     va_start(args, fmt);
255     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
256     va_end(args);
257     g_warning("\n");
258 #endif
261 void msg(const char *fmt, ...)
263 #if 0
264     va_list args;
265     fprintf(stdout, "JavaBinderyImpl:");
266     va_start(args, fmt);
267     vfprintf(stdout, fmt, args);
268     va_end(args);
269     fprintf(stdout, "\n");
270 #else
271     va_list args;
272     g_message("JavaBinderyImpl:");
273     va_start(args, fmt);
274     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
275     va_end(args);
276     g_message("\n");
277 #endif
282 //########################################################################
283 //# W I N 3 2      S T Y L E
284 //########################################################################
285 #ifdef __WIN32__
288 #define DIR_SEPARATOR "\\"
289 #define PATH_SEPARATOR ";"
293 static bool getRegistryString(HKEY /*root*/, const char *keyName,
294                const char *valName, char *buf, int buflen)
296     HKEY key;
297     DWORD bufsiz  = buflen;
298     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
299     int ret = RegQueryValueEx(key, TEXT(valName),
300             NULL, NULL, (BYTE *)buf, &bufsiz);
301     if (ret != ERROR_SUCCESS)
302         {
303         err("Key '%s\\%s not found\n", keyName, valName);
304         return false;
305         }
306     RegCloseKey(key);
307     return true;
311 static String cleanPath(const String &s)
313     String buf;
314     for (unsigned int i=0 ; i<s.size() ; i++)
315         {
316         char ch = s[i];
317         if (ch != '"')
318             buf.push_back(ch);
319                 }
320         return buf;
324 /**
325  * Common places to find jvm.dll under JAVA_HOME
326  */ 
327 static const char *commonJavaPaths[] =
329     "\\jre\\bin\\client\\jvm.dll",
330     "\\bin\\client\\jvm.dll",
331     "\\jvm.dll",
332     NULL
333 };
336 /**
337  * Return the directory of the .exe that is currently running
338  */
339 static String getExePath()
341     char exeName[MAX_PATH+1];
342     GetModuleFileName(NULL, exeName, MAX_PATH);
343     char *slashPos = strrchr(exeName, '\\');
344     if (slashPos)
345         *slashPos = '\0';
346     String s = exeName;
347     return s;
351 /**
352  * Check a directory for several possibilities of sub-locations
353  * under it, where a jvm might exist.
354  */
355 static String checkPathUnderRoot(const String &root)
357     for (const char **path = commonJavaPaths ; *path ; path++)
358         {
359         String jpath = root;
360         jpath.append(*path);
361         //msg("trying '%s'", jpath.c_str());
362         struct stat finfo;
363         if (stat(jpath.c_str(), &finfo)>=0)
364             {
365             //msg("found");
366             return jpath;
367             }
368         }
369     return "";
374 /**
375  * Attempt to find and load a jvm.dll file.  Find the createVM()
376  * function's address and return it
377  */
378 static CreateVMFunc getCreateVMFunc()
380     bool found = false;
381     String libname;
383     /**
384      * First, look for an embedded jre in the .exe's dir.
385      * This allows us to package our own JRE if we want to.
386      */
387     String inkscapeHome = getExePath();
388     inkscapeHome.append("\\jre");
389     msg("INKSCAPE_HOME='%s'", inkscapeHome.c_str());
390     String path = checkPathUnderRoot(inkscapeHome);
391     if (path.size() > 0)
392         {
393         libname = path;
394         found = true;
395         }
397     /**
398      * Next, look for JAVA_HOME.  This will allow the user
399      * to override what's in the registry
400      */
401     if (!found)
402         {
403         const char *envStr = getenv("JAVA_HOME");
404         if (envStr)
405             {
406             String javaHome = cleanPath(envStr);
407             msg("JAVA_HOME='%s'", javaHome.c_str());
408             path = checkPathUnderRoot(javaHome);
409             if (path.size() > 0)
410                 {
411                 libname = path;
412                 found = true;
413                 }
414             }
415         }
417     //not at JAVA_HOME.  check the registry
418     if (!found)
419         {
420         char verbuf[16];
421         char regpath[80];
422         strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
423         bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
424                      regpath, "CurrentVersion", verbuf, 15);
425         if (!ret)
426             {
427             msg("JVM CurrentVersion not found in registry at '%s'", regpath);
428             }
429         else
430             {
431             strcat(regpath, "\\");
432             strcat(regpath, verbuf);
433             //msg("reg path: %s\n", regpath);
434             char valbuf[80];
435             ret = getRegistryString(HKEY_LOCAL_MACHINE,
436                      regpath, "RuntimeLib", valbuf, 79);
437             if (ret)
438                 {
439                 found = true;
440                 libname = valbuf;
441                 }
442             else
443                 {
444                 msg("JVM RuntimeLib not found in registry at '%s'",
445                                           regpath);
446                                 }
447                         }
448         }
450     if (!found)
451         {
452         err("JVM not found at JAVA_HOME or in registry");
453         return NULL;
454         }
456     /**
457      * If we are here, then we seem to have a valid path for jvm.dll
458      * Give it a try
459      */              
460     msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
461     HMODULE lib = LoadLibrary(libname.c_str());
462     if (!lib)
463         {
464         err("Java VM not found at '%s'", libname.c_str());
465         return NULL;
466         }
467     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
468     if (!createVM)
469         {
470         err("Could not find 'JNI_CreateJavaVM' in shared library '%s'",
471                                    libname.c_str());
472         return NULL;
473         }
474     return createVM;
477 /**
478  * Return the directory where the Java classes/libs/resources are
479  * located
480  */
481 static void getJavaRoot(String &javaroot)
483     /*
484         javaroot = getExePath();
485     javaroot.append("\\");
486     javaroot.append(INKSCAPE_BINDDIR);
487     javaroot.append("\\java");
488     */
489     javaroot = INKSCAPE_BINDDIR;
490     javaroot.append("\\java");
496 //########################################################################
497 //# U N I X    S T Y L E
498 //########################################################################
499 #else /* !__WIN32__ */
502 #define DIR_SEPARATOR "/"
503 #define PATH_SEPARATOR ":"
506 /**
507  * Recursively descend into a directory looking for libjvm.so
508  */
509 static bool findJVMRecursive(const String &dirpath,
510                              std::vector<String> &results)
512     DIR *dir = opendir(dirpath.c_str());
513     if (!dir)
514         return false;
515     bool ret = false;
516     while (true)
517         {
518         struct dirent *de = readdir(dir);
519         if (!de)
520             break;
521         String fname = de->d_name;
522         if (fname == "." || fname == "..")
523             continue;
524         String path = dirpath;
525         path.push_back('/');
526         path.append(fname);
527         if (fname == "libjvm.so")
528             {
529             ret = true;
530             results.push_back(path);
531             continue;
532             }
533         struct stat finfo;
534         if (lstat(path.c_str(), &finfo)<0)
535             {
536             break;
537             }
538         if (finfo.st_mode & S_IFDIR)
539             {
540             ret |= findJVMRecursive(path, results);
541             }
542         }
543     closedir(dir);
544     return ret;
548 /**
549  * Some common places on a Unix filesystem where JVMs are
550  * often found.
551  */
552 static const char *commonJavaPaths[] =
554     "/usr/lib/jvm/jre",
555     "/usr/lib/jvm",
556     "/usr/local/lib/jvm/jre",
557     "/usr/local/lib/jvm",
558     "/usr/java",
559     "/usr/local/java",
560     NULL
561 };
565 /**
566  * Look for a Java VM (libjvm.so) in several Unix places
567  */
568 static bool findJVM(String &result)
570     std::vector<String> results;
571     int found = false;
573     /* Is there one specified by the user? */
574     const char *javaHome = getenv("JAVA_HOME");
575     if (javaHome && findJVMRecursive(javaHome, results))
576         found = true;
577     else for (const char **path = commonJavaPaths ; *path ; path++)
578         {
579         if (findJVMRecursive(*path, results))
580             {
581             found = true;
582             break;
583             }
584         }
585     if (!found)
586         {
587         return false;
588         }
589     if (results.size() == 0)
590         return false;
591     //Look first for a Client VM
592     for (unsigned int i=0 ; i<results.size() ; i++)
593         {
594         String s = results[i];
595         if (s.find("client") != s.npos)
596             {
597             result = s;
598             return true;
599             }
600         }
601     //else default to the first
602     result = results[0];
603     return true;
608 /**
609  * Attempt to find and load a jvm.dll file.  Find the createVM()
610  * function's address and return it
611  */
612 static CreateVMFunc getCreateVMFunc()
614     String libname;
615     if (!findJVM(libname))
616         {
617         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
618         return NULL;
619         }
620     msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
621     void *lib = dlopen(libname.c_str(), RTLD_NOW);
622     if (!lib)
623         {
624         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
625         return NULL;
626         }
627     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
628     if (!createVM)
629         {
630         err("Could not find 'JNI_CreateJavaVM' in shared library");
631             return NULL;
632         }
633     return createVM;
637 /**
638  * Return the directory where the Java classes/libs/resources are
639  * located
640  */
641 static void getJavaRoot(String &javaroot)
643     javaroot = INKSCAPE_BINDDIR;
644     javaroot.append("/java");
647 #endif /* !__WIN32__ */
650 //########################################################################
651 //# COMMON
652 //########################################################################
655 bool JavaBinderyImpl::isLoaded()
657     return (jvm != (void *)0);
662 /**
663  * This will set up the classpath for the launched VM.
664  * We will add two things:
665  *   1.  INKSCAPE_JAVADIR/classes -- path to loose classes
666  *   2.  A concatenation of all jar files in INKSCAPE_JAVADIR/lib
667  *
668  * This will allow people to add classes and jars to the JVM without
669  * needing to state them explicitly.
670  * 
671  * @param javaroot.  Should be INKSCAPE_JAVADIR
672  * @param result a string buffer to hold the result of this method   
673  */        
674 static void populateClassPath(const String &javaroot,
675                               String &result)
677     String classdir = javaroot;
678     classdir.append(DIR_SEPARATOR);
679     classdir.append("classes");
681     String cp = classdir;
683     String libdir = javaroot;
684     libdir.append(DIR_SEPARATOR);
685     libdir.append("lib");
687     DIR *dir = opendir(libdir.c_str());
688     if (!dir)
689         {
690         result = cp;
691         return;
692         }
694     while (true)
695         {
696         struct dirent *de = readdir(dir);
697         if (!de)
698             break;
699         String fname = de->d_name;
700         if (fname == "." || fname == "..")
701             continue;
702         if (fname.size()<5) //x.jar
703             continue;
704         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
705             continue;
707         String path = libdir;
708         path.append(DIR_SEPARATOR);
709         path.append(fname);
711         cp.append(PATH_SEPARATOR);
712         cp.append(path);
713         }
714     closedir(dir);
715     
716     result = cp;
721 //========================================================================
722 // Gateway
723 //========================================================================
724 /**
725  * This is provided to scripts can grab the current copy or the
726  * repr tree.  If anyone has a smarter way of doing this, please implement. 
727  */    
728 jstring JNICALL documentGet(JNIEnv *env, jobject /*obj*/, jlong /*ptr*/)
730     //JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
731     String buf =  sp_repr_save_buf((SP_ACTIVE_DOCUMENT)->rdoc);
732     jstring jstr = env->NewStringUTF(buf.c_str());
733     return jstr;
736 /**
737  * This is provided to scripts can load an XML tree into Inkscape.
738  * If anyone has a smarter way of doing this, please implement. 
739  */    
740 jboolean JNICALL documentSet(JNIEnv */*env*/, jobject /*obj*/, jlong /*ptr*/, jstring /*jstr*/)
742     /*
743     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
744     String s = getString(env, jstr);
745     SPDocument *doc = sp_document_new_from_mem(s.c_str(), s.size(), true);
746     */
747     return JNI_TRUE;
750 /**
751  * This method is used to allow the gateway class to
752  * redirect its logging stream here.
753  * For the main C++/Java bindings, see dobinding.cpp 
754  */    
755 void JNICALL logWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
757     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
758     bind->log(ch);
762 static JNINativeMethod gatewayMethods[] =
764 { (char *)"documentGet", (char *)"(J)Ljava/lang/String;",  (void *)documentGet },
765 { (char *)"documentSet", (char *)"(JLjava/lang/String;)Z", (void *)documentSet },
766 { (char *)"logWrite",    (char *)"(JI)V",                  (void *)logWrite    },
767 { NULL,  NULL, NULL }
768 };
771 /**
772  * This sets up the 'Gateway' java class for execution of
773  * scripts.   The class's constructor takes a jlong.  This java long
774  * is used to store the pointer to 'this'.  When ScriptRunner makes
775  * native calls, it passes that jlong back, so that it can call the
776  * methods of this C++ class.  
777  */  
778 bool JavaBinderyImpl::setupGateway()
780     String className = "org/inkscape/cmn/Gateway";
781     if (!registerNatives(className, gatewayMethods))
782         {
783         return false;
784         }
785     jclass cls = env->FindClass(className.c_str());
786     if (!cls)
787         {
788         err("setupGateway: cannot find class '%s' : %s",
789                          className.c_str(), getException().c_str());
790         return false;
791                 }
792         jmethodID mid = env->GetMethodID(cls, "<init>", "(J)V");
793         if (!mid)
794         {
795         err("setupGateway: cannot find constructor for '%s' : %s",
796                           className.c_str(), getException().c_str());
797         return false;
798                 }
799     gatewayObj = env->NewObject(cls, mid, ((jlong)this));
800     if (!gatewayObj)
801         {
802         err("setupGateway: cannot construct '%s' : %s",
803                          className.c_str(), getException().c_str());
804         return false;
805                 }
807         msg("Gateway ready");
808     return true;
811 bool JavaBinderyImpl::scriptRun(const String &lang, const String &script)
813     if (!loadJVM())
814         return false;
816     std::vector<Value> params;
817     Value langParm(lang);
818     params.push_back(langParm);
819     Value scriptParm(script);
820     params.push_back(scriptParm);
821     Value retval;
822     callInstance(Value::BIND_VOID, gatewayObj, "scriptRun",
823              "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
824     return retval.getBoolean();
827 bool JavaBinderyImpl::scriptRunFile(const String &lang, const String &fname)
829     if (!loadJVM())
830         return false;
832     std::vector<Value> params;
833     Value langParm(lang);
834     params.push_back(langParm);
835     Value fnameParm(fname);
836     params.push_back(fnameParm);
837     Value retval;
838     callInstance(Value::BIND_VOID, gatewayObj, "scriptRunFile",
839              "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
840     return retval.getBoolean();
843 bool JavaBinderyImpl::showConsole()
845     if (!loadJVM())
846         return false;
847         
848     std::vector<Value> params;
849     Value retval;
850     callInstance(Value::BIND_VOID, gatewayObj, "showConsole",
851              "()Z", params, retval);
852     return retval.getBoolean();
856 //========================================================================
857 // End Gateway
858 //========================================================================
861 /**
862  * This is used to grab output from the VM itself. See 'options' below.
863  */ 
864 static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args)
866     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
867     return JNI_TRUE;
871 /**
872  * This is the most important part of this class.  Here we
873  * attempt to find, load, and initialize a java (or mlvm?) virtual
874  * machine.
875  * 
876  * @return true if successful, else false    
877  */ 
878 bool JavaBinderyImpl::loadJVM()
880     if (jvm)
881         return true;
883     CreateVMFunc createVM = getCreateVMFunc();
884     if (!createVM)
885         {
886         err("Could not find 'JNI_CreateJavaVM' in shared library");
887         return false;
888         }
890     String javaroot;
891     getJavaRoot(javaroot);
892     String cp;
893     populateClassPath(javaroot, cp);
894     String classpath = "-Djava.class.path=";
895     classpath.append(normalizePath(cp));
896     msg("Class path is: '%s'", classpath.c_str());
898     String libpath = "-Djava.library.path=";
899     libpath.append(javaroot);
900     libpath.append(DIR_SEPARATOR);
901     libpath.append("libm");
902     libpath = normalizePath(libpath);
903     msg("Lib path is: '%s'", libpath.c_str());
905     JavaVMInitArgs vm_args;
906     JavaVMOption options[10];//should be enough
907     int nOptions = 0;
908     options[nOptions++].optionString = (char *)classpath.c_str();
909     options[nOptions++].optionString = (char *)libpath.c_str();
910     //options[nOptions++].optionString = (char *)"-verbose:jni";
911     options[nOptions  ].optionString = (char *)"vfprintf";
912     options[nOptions++].extraInfo    = (void *)vfprintfHook;
913     vm_args.version                  = JNI_VERSION_1_4;
914     vm_args.options                  = options;
915     vm_args.nOptions                 = nOptions;
916     vm_args.ignoreUnrecognized       = true;
918     if (createVM(&jvm, &env, &vm_args) < 0)
919         {
920         err("JNI_CreateJavaVM() failed");
921         return false;
922         }
924     //get jvm version
925     jint vers = env->GetVersion();
926     int versionMajor = (vers>>16) & 0xffff;
927     int versionMinor = (vers    ) & 0xffff;
928     msg("Loaded JVM version %d.%d", versionMajor, versionMinor);
930     if (!setupGateway()) {
931         // set jvm = NULL, otherwise, this method will return true when called for the second time while the gateway might not have been created!
932         jvm->DestroyJavaVM();
933         jvm = NULL;
934         env = NULL;
935         err("Java bindings: setupGateway() failed");
936         return false;
937     }
939     return true;
943 /**
944  *  This is a difficult method.  What we are doing is trying to
945  *  call a static method with a list of arguments.  Similar to 
946  *  a varargs call, we need to marshal the Values into their
947  *  Java equivalents and make the proper call.
948  *  
949  * @param type the return type of the method
950  * @param className the full (package / name) name of the java class
951  * @param methodName the name of the method being invoked
952  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
953  *    that describes the param and return types of the method.
954  * @param retval the return value of the java method
955  * @return true if the call was successful, else false.  This is not
956  *    the return value of the method.    
957  */    
958 bool JavaBinderyImpl::callStatic(int type,
959                         const String &className,
960                         const String &methodName,
961                         const String &signature,
962                         const std::vector<Value> &params,
963                         Value &retval)
965     jclass cls = env->FindClass(className.c_str());
966     if (!cls)
967         {
968         err("Could not find class '%s' : %s",
969                        className.c_str(), getException().c_str());
970         return false;
971         }
972     jmethodID mid = env->GetStaticMethodID(cls,
973                 methodName.c_str(), signature.c_str());
974     if (!mid)
975         {
976         err("Could not find method '%s:%s/%s' : %s",
977                         className.c_str(), methodName.c_str(),
978                             signature.c_str(), getException().c_str());
979         return false;
980         }
981     /**
982      * Assemble your parameters into a form usable by JNI
983      */
984     jvalue *jvals = new jvalue[params.size()];
985     for (unsigned int i=0 ; i<params.size() ; i++)
986         {
987         Value v = params[i];
988         switch (v.getType())
989             {
990             case Value::BIND_BOOLEAN:
991                 {
992                 jvals[i].z = (jboolean)v.getBoolean();
993                 break;
994                 }
995             case Value::BIND_INT:
996                 {
997                 jvals[i].i = (jint)v.getInt();
998                 break;
999                 }
1000             case Value::BIND_DOUBLE:
1001                 {
1002                 jvals[i].d = (jdouble)v.getDouble();
1003                 break;
1004                 }
1005             case Value::BIND_STRING:
1006                 {
1007                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
1008                 break;
1009                 }
1010             default:
1011                 {
1012                 err("Unknown value type: %d", v.getType());
1013                 return false;
1014                 }
1015             }
1016         }
1017     switch (type)
1018         {
1019         case Value::BIND_VOID:
1020             {
1021             env->CallStaticVoidMethodA(cls, mid, jvals);
1022             break;
1023             }
1024         case Value::BIND_BOOLEAN:
1025             {
1026             jboolean ret = env->CallStaticBooleanMethodA(cls, mid, jvals);
1027             if (ret == JNI_TRUE) //remember, don't truncate
1028                 retval.setBoolean(true);
1029             else
1030                 retval.setBoolean(false);
1031             break;
1032             }
1033         case Value::BIND_INT:
1034             {
1035             jint ret = env->CallStaticIntMethodA(cls, mid, jvals);
1036             retval.setInt(ret);
1037             break;
1038             }
1039         case Value::BIND_DOUBLE:
1040             {
1041             jdouble ret = env->CallStaticDoubleMethodA(cls, mid, jvals);
1042             retval.setDouble(ret);
1043             break;
1044             }
1045         case Value::BIND_STRING:
1046             {
1047             jobject ret = env->CallStaticObjectMethodA(cls, mid, jvals);
1048             jstring jstr = (jstring) ret;
1049             const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1050             retval.setString(str);
1051             env->ReleaseStringUTFChars(jstr, str);
1052             break;
1053             }
1054         default:
1055             {
1056             err("Unknown return type: %d", type);
1057             return false;
1058             }
1059         }
1060     delete jvals;
1061     String errStr = getException();
1062     if (errStr.size()>0)
1063         {
1064         err("callStatic: %s", errStr.c_str());
1065         return false;
1066                 }
1067     return true;
1072 /**
1073  *  Another difficult method.  However, this time we are operating
1074  *  on an existing instance jobject. 
1075  *  
1076  * @param type the return type of the method
1077  * @param obj the instance upon which to make the call
1078  * @param methodName the name of the method being invoked
1079  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
1080  *    that describes the param and return types of the method.
1081  * @param retval the return value of the java method
1082  * @return true if the call was successful, else false.  This is not
1083  *    the return value of the method.    
1084  */    
1085 bool JavaBinderyImpl::callInstance(
1086                         int type,
1087                         const jobject obj,
1088                         const String &methodName,
1089                         const String &signature,
1090                         const std::vector<Value> &params,
1091                         Value &retval)
1093     jmethodID mid = env->GetMethodID(env->GetObjectClass(obj),
1094                 methodName.c_str(), signature.c_str());
1095     if (!mid)
1096         {
1097         err("Could not find method '%s/%s' : %s",
1098                         methodName.c_str(),
1099                             signature.c_str(), getException().c_str());
1100         return false;
1101         }
1102     /**
1103      * Assemble your parameters into a form usable by JNI
1104      */
1105     jvalue *jvals = new jvalue[params.size()];
1106     for (unsigned int i=0 ; i<params.size() ; i++)
1107         {
1108         Value v = params[i];
1109         switch (v.getType())
1110             {
1111             case Value::BIND_BOOLEAN:
1112                 {
1113                 jvals[i].z = (jboolean)v.getBoolean();
1114                 break;
1115                 }
1116             case Value::BIND_INT:
1117                 {
1118                 jvals[i].i = (jint)v.getInt();
1119                 break;
1120                 }
1121             case Value::BIND_DOUBLE:
1122                 {
1123                 jvals[i].d = (jdouble)v.getDouble();
1124                 break;
1125                 }
1126             case Value::BIND_STRING:
1127                 {
1128                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
1129                 break;
1130                 }
1131             default:
1132                 {
1133                 err("Unknown value type: %d", v.getType());
1134                 return false;
1135                 }
1136             }
1137         }
1138     switch (type)
1139         {
1140         case Value::BIND_VOID:
1141             {
1142             env->CallVoidMethodA(obj, mid, jvals);
1143             break;
1144             }
1145         case Value::BIND_BOOLEAN:
1146             {
1147             jboolean ret = env->CallBooleanMethodA(obj, mid, jvals);
1148             if (ret == JNI_TRUE) //remember, don't truncate
1149                 retval.setBoolean(true);
1150             else
1151                 retval.setBoolean(false);
1152             break;
1153             }
1154         case Value::BIND_INT:
1155             {
1156             jint ret = env->CallIntMethodA(obj, mid, jvals);
1157             retval.setInt(ret);
1158             break;
1159             }
1160         case Value::BIND_DOUBLE:
1161             {
1162             jdouble ret = env->CallDoubleMethodA(obj, mid, jvals);
1163             retval.setDouble(ret);
1164             break;
1165             }
1166         case Value::BIND_STRING:
1167             {
1168             jobject ret = env->CallObjectMethodA(obj, mid, jvals);
1169             jstring jstr = (jstring) ret;
1170             const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1171             retval.setString(str);
1172             env->ReleaseStringUTFChars(jstr, str);
1173             break;
1174             }
1175         default:
1176             {
1177             err("Unknown return type: %d", type);
1178             return false;
1179             }
1180         }
1181     delete jvals;
1182     String errStr = getException();
1183     if (errStr.size()>0)
1184         {
1185         err("callStatic: %s", errStr.c_str());
1186         return false;
1187                 }
1188     return true;
1194 /**
1195  * Fetch the last exception from the JVM, if any.  Clear it to
1196  * continue processing
1197  * 
1198  * @return the exception's descriptio,if any.  Else ""  
1199  */  
1200 String JavaBinderyImpl::getException()
1202     return getExceptionString(env);
1207 /**
1208  * Convenience method to call the static void main(String argv[])
1209  * method of a given class
1210  * 
1211  * @param className full name of the java class
1212  * @args the argument strings to the method 
1213  * @return true if successful, else false   
1214  */ 
1215 bool JavaBinderyImpl::callMain(const String &className,
1216                                const std::vector<String> &args)
1218     std::vector<Value> parms;
1219     for (unsigned int i=0 ; i<args.size() ; i++)
1220         {
1221         Value v;
1222         v.setString(args[i]);
1223         parms.push_back(v);
1224                 }
1225     Value retval;
1226     return callStatic(Value::BIND_VOID, className, "main",
1227              "([Ljava/lang/String;)V", parms, retval);
1231 /**
1232  * Used to register an array of native methods for a named class
1233  * 
1234  * @param className the full name of the java class
1235  * @param the method array
1236  * @return true if successful, else false     
1237  */ 
1238 bool JavaBinderyImpl::registerNatives(const String &className,
1239                            const JNINativeMethod *methods)
1241     jclass cls = env->FindClass(className.c_str());
1242     if (!cls)
1243         {
1244         err("Could not find class '%s'", className.c_str());
1245         return false;
1246         }
1247     //msg("registerNatives: class '%s' found", className.c_str());
1248     
1249     /**
1250      * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
1251      */
1252         jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
1253                   "()[Ljava/lang/reflect/Constructor;");
1254         if (!mid)
1255             {
1256             err("Could not get reflect mid for 'getConstructors' : %s",
1257              getException().c_str());
1258                 return false;
1259                 }
1260         jobject res = env->CallObjectMethod(cls, mid);
1261         if (!res)
1262             {
1263             err("Could not get constructors : %s", getException().c_str());
1264                 return false;
1265                 }
1266         /**
1267          * end hack
1268          */             
1269     jint nrMethods = 0;
1270     for (const JNINativeMethod *m = methods ; m->name ; m++)
1271         nrMethods++;
1272     jint ret = env->RegisterNatives(cls, methods, nrMethods);
1273     if (ret < 0)
1274         {
1275         err("Could not register %d native methods for '%s' : %s",
1276                     nrMethods, className.c_str(), getException().c_str());
1277         return false;
1278         }
1279     return true;
1285 } // namespace Bind
1286 } // namespace Inkscape
1288 //########################################################################
1289 //# E N D    O F    F I L E
1290 //########################################################################