Code

3d68bdc15872d36b3f1be50b6c3196fde1ad22eb
[inkscape.git] / src / bind / javabind.cpp
1 /**
2  * This is a simple mechanism to bind Inkscape to Java, and thence
3  * to all of the nice things that can be layered upon that.
4  *
5  * Authors:
6  *   Bob Jamison
7  *
8  * Copyright (C) 2007-2008 Bob Jamison
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 3 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23  */
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <string.h>
33 #include <jni.h>
35 #include <sys/types.h>
36 #include <dirent.h>
39 #ifdef __WIN32__
40 #include <windows.h>
41 #else
42 #include <dlfcn.h>
43 #include <errno.h>
44 #endif
46 #if HAVE_SYS_STAT_H
47 #include <sys/stat.h>
48 #endif
50 #include "javabind.h"
51 #include "javabind-private.h"
52 #include <path-prefix.h>
53 #include <prefix.h>
54 #include <glib/gmessages.h>
57 /**
58  * Note: We must limit Java or JVM-specific code to this file
59  * and to dobinding.cpp.  It should be hidden from javabind.h
60  */  
63 namespace Inkscape
64 {
66 namespace Bind
67 {
70 //########################################################################
71 //# DEFINITIONS
72 //########################################################################
74 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
78 //########################################################################
79 //# UTILITY
80 //########################################################################
82 /**
83  * Normalize path.  Java wants '/', even on Windows
84  */ 
85 String normalizePath(const String &str)
86 {
87     String buf;
88     for (unsigned int i=0 ; i<str.size() ; i++)
89         {
90         char ch = str[i];
91         if (ch == '\\')
92             buf.push_back('/');
93         else
94             buf.push_back(ch);
95                 }
96         return buf;
97 }
100 String getException(JNIEnv *env)
102     String buf;
103     jthrowable exc = env->ExceptionOccurred();
104     if (!exc)
105         return buf;
106     jclass cls = env->GetObjectClass(exc);
107     jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
108     jstring jstr = (jstring) env->CallObjectMethod(exc, mid);
109     const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
110     buf.append(str);
111     env->ReleaseStringUTFChars(jstr, str);
112     env->ExceptionClear();
113         return buf;
116 jint getInt(JNIEnv *env, jobject obj, const char *name)
118     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
119     return env->GetIntField(obj, fid);
122 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
124     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
125     env->SetIntField(obj, fid, val);
128 jlong getLong(JNIEnv *env, jobject obj, const char *name)
130     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
131     return env->GetLongField(obj, fid);
134 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
136     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
137     env->SetLongField(obj, fid, val);
140 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
142     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
143     return env->GetFloatField(obj, fid);
146 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
148     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
149     env->SetFloatField(obj, fid, val);
152 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
154     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
155     return env->GetDoubleField(obj, fid);
158 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
160     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
161     env->SetDoubleField(obj, fid, val);
164 String getString(JNIEnv *env, jobject obj, const char *name)
166     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
167     jstring jstr = (jstring)env->GetObjectField(obj, fid);
168     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
169     String str = chars;
170     env->ReleaseStringUTFChars(jstr, chars);
171     return str;
174 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
176     jstring jstr = env->NewStringUTF(val.c_str());
177     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
178     env->SetObjectField(obj, fid, jstr);
184 //########################################################################
185 //# CONSTRUCTOR/DESTRUCTOR
186 //########################################################################
188 static JavaBinderyImpl *_instance = NULL;
190 JavaBindery *JavaBindery::getInstance()
192     return JavaBinderyImpl::getInstance();
195 JavaBinderyImpl *JavaBinderyImpl::getInstance()
197     if (!_instance)
198         {
199         _instance = new JavaBinderyImpl();
200         }
201     return _instance;
204 JavaBinderyImpl::JavaBinderyImpl()
206     jvm  = NULL;
207     env  = NULL;
210 JavaBinderyImpl::~JavaBinderyImpl()
215 //########################################################################
216 //# MESSAGES
217 //########################################################################
219 void err(const char *fmt, ...)
221 #if 0
222     va_list args;
223     fprintf(stderr, "JavaBinderyImpl err:");
224     va_start(args, fmt);
225     vfprintf(stderr, fmt, args);
226     va_end(args);
227     fprintf(stderr, "\n");
228 #else
229     va_list args;
230     g_warning("JavaBinderyImpl err:");
231     va_start(args, fmt);
232     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
233     va_end(args);
234     g_warning("\n");
235 #endif
238 void msg(const char *fmt, ...)
240 #if 0
241     va_list args;
242     fprintf(stdout, "JavaBinderyImpl:");
243     va_start(args, fmt);
244     vfprintf(stdout, fmt, args);
245     va_end(args);
246     fprintf(stdout, "\n");
247 #else
248     va_list args;
249     g_message("JavaBinderyImpl:");
250     va_start(args, fmt);
251     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
252     va_end(args);
253     g_message("\n");
254 #endif
259 //########################################################################
260 //# W I N 3 2      S T Y L E
261 //########################################################################
262 #ifdef __WIN32__
265 #define DIR_SEPARATOR "\\"
266 #define PATH_SEPARATOR ";"
270 static bool getRegistryString(HKEY root, const char *keyName,
271                const char *valName, char *buf, int buflen)
273     HKEY key;
274     DWORD bufsiz  = buflen;
275     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
276     int ret = RegQueryValueEx(key, TEXT(valName),
277             NULL, NULL, (BYTE *)buf, &bufsiz);
278     if (ret != ERROR_SUCCESS)
279         {
280         err("Key '%s\\%s not found\n", keyName, valName);
281         return false;
282         }
283     RegCloseKey(key);
284     return true;
288 static CreateVMFunc getCreateVMFunc()
290     char verbuf[16];
291     char regpath[80];
292     strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
293     bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
294                      regpath, "CurrentVersion", verbuf, 15);
295     if (!ret)
296         {
297         err("JVM CurrentVersion not found in registry\n");
298         return NULL;
299         }
300     strcat(regpath, "\\");
301     strcat(regpath, verbuf);
302     //msg("reg path: %s\n", regpath);
303     char libname[80];
304     ret = getRegistryString(HKEY_LOCAL_MACHINE,
305                      regpath, "RuntimeLib", libname, 79);
306     if (!ret)
307         {
308         err("Current JVM RuntimeLib not found in registry\n");
309         return NULL;
310         }
311     //msg("jvm path: %s\n", libname);
312     HMODULE lib = LoadLibrary(libname);
313     if (!lib)
314         {
315         err("Java VM not found at '%s'", libname);
316         return NULL;
317         }
318     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
319     if (!createVM)
320         {
321         err("Could not find 'JNI_CreateJavaVM' in shared library");
322         return NULL;
323         }
324     return createVM;
327 static void getJavaRoot(String &javaroot)
329     char exeName[80];
330     GetModuleFileName(NULL, exeName, 80);
331     char *slashPos = strrchr(exeName, '\\');
332     if (slashPos)
333         *slashPos = '\0';
334     javaroot = exeName;
335     javaroot.append("\\");
336     javaroot.append(INKSCAPE_JAVADIR);
342 //########################################################################
343 //# U N I X    S T Y L E
344 //########################################################################
345 #else /* !__WIN32__ */
348 #define DIR_SEPARATOR "/"
349 #define PATH_SEPARATOR ":"
352 /**
353  * Recursively descend into a directory looking for libjvm.so
354  */
355 static bool findJVMRecursive(const String &dirpath,
356                              std::vector<String> &results)
358     DIR *dir = opendir(dirpath.c_str());
359     if (!dir)
360         return false;
361     bool ret = false;
362     while (true)
363         {
364         struct dirent *de = readdir(dir);
365         if (!de)
366             break;
367         String fname = de->d_name;
368         if (fname == "." || fname == "..")
369             continue;
370         String path = dirpath;
371         path.push_back('/');
372         path.append(fname);
373         if (fname == "libjvm.so")
374             {
375             ret = true;
376             results.push_back(path);
377             continue;
378             }
379         struct stat finfo;
380         if (lstat(path.c_str(), &finfo)<0)
381             {
382             break;
383             }
384         if (finfo.st_mode & S_IFDIR)
385             {
386             ret |= findJVMRecursive(path, results);
387             }
388         }
389     closedir(dir);
390     return ret;
394 static const char *commonJavaPaths[] =
396     "/usr/java",
397     "/usr/local/java",
398     "/usr/lib/jvm",
399     "/usr/local/lib/jvm",
400     NULL
401 };
403 /**
404  * Look for a Java VM (libjvm.so) in several Unix places
405  */
406 static bool findJVM(String &result)
408     std::vector<String> results;
409     int found = false;
411     /* Is there one specified by the user? */
412     const char *javaHome = getenv("JAVA_HOME");
413     if (javaHome && findJVMRecursive(javaHome, results))
414         found = true;
415     else for (const char **path = commonJavaPaths ; *path ; path++)
416         {
417         if (findJVMRecursive(*path, results))
418             {
419             found = true;
420             break;
421             }
422         }
423     if (!found)
424         {
425         return false;
426         }
427     if (results.size() == 0)
428         return false;
429     //Look first for a Client VM
430     for (unsigned int i=0 ; i<results.size() ; i++)
431         {
432         String s = results[i];
433         if (s.find("client") != s.npos)
434             {
435             result = s;
436             return true;
437             }
438         }
439     //else default to the first
440     result = results[0];
441     return true;
446 static CreateVMFunc getCreateVMFunc()
448     String libname;
449     if (!findJVM(libname))
450         {
451         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
452         return NULL;
453         }
454     void *lib = dlopen(libname.c_str(), RTLD_NOW);
455     if (!lib)
456         {
457         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
458         return NULL;
459         }
460     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
461     if (!createVM)
462         {
463         err("Could not find 'JNI_CreateJavaVM' in shared library");
464             return NULL;
465         }
466     return createVM;
470 static void getJavaRoot(String &javaroot)
472     javaroot = INKSCAPE_JAVADIR;
475 #endif /* !__WIN32__ */
478 //########################################################################
479 //# COMMON
480 //########################################################################
483 bool JavaBinderyImpl::isLoaded()
485     return (jvm != (void *)0);
490 /**
491  * This will set up the classpath for the launched VM.
492  * We will add two things:
493  *   1.  INKSCAPE_JAVADIR/classes -- path to loose classes
494  *   2.  A concatenation of all jar files in INKSCAPE_JAVADIR/lib
495  *
496  * This will allow people to add classes and jars to the JVM without
497  * needing to state them explicitly.
498  * 
499  * @param javaroot.  Should be INKSCAPE_JAVADIR
500  * @param result a string buffer to hold the result of this method   
501  */        
502 static void populateClassPath(const String &javaroot,
503                               String &result)
505     String classdir = javaroot;
506     classdir.append(DIR_SEPARATOR);
507     classdir.append("classes");
509     String cp = classdir;
511     String libdir = javaroot;
512     libdir.append(DIR_SEPARATOR);
513     libdir.append("lib");
515     DIR *dir = opendir(libdir.c_str());
516     if (!dir)
517         {
518         result = cp;
519         return;
520         }
522     while (true)
523         {
524         struct dirent *de = readdir(dir);
525         if (!de)
526             break;
527         String fname = de->d_name;
528         if (fname == "." || fname == "..")
529             continue;
530         if (fname.size()<5) //x.jar
531             continue;
532         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
533             continue;
535         String path = libdir;
536         path.append(DIR_SEPARATOR);
537         path.append(fname);
539         cp.append(PATH_SEPARATOR);
540         cp.append(path);
541         }
542     closedir(dir);
543     
544     result = cp;
549 //========================================================================
550 // Native methods
551 //========================================================================
552 /**
553  * These methods are used to allow the ScriptRunner class to
554  * redirect its stderr and stdout streams to here, to be caught
555  * by two string buffers.  We can then use those buffers how we
556  * want.  These native methods are only those needed for running
557  * a script.  For the main C++/Java bindings, see dobinding.cpp 
558  */    
559 void JNICALL stdOutWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
561     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
562     bind->stdOut(ch);
565 void JNICALL stdErrWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
567     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
568     bind->stdErr(ch);
572 static JNINativeMethod scriptRunnerMethods[] =
574 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
575 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
576 { NULL,  NULL, NULL }
577 };
578 //========================================================================
579 // End native methods
580 //========================================================================
583 /**
584  * This is used to grab output from the VM itself. See 'options' below.
585  */ 
586 static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args)
588     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
592 /**
593  * This is the most important part of this class.  Here we
594  * attempt to find, load, and initialize a java (or mlvm?) virtual
595  * machine.
596  * 
597  * @return true if successful, else false    
598  */ 
599 bool JavaBinderyImpl::loadJVM()
601     if (jvm)
602         return true;
604     CreateVMFunc createVM = getCreateVMFunc();
605     if (!createVM)
606         {
607         err("Could not find 'JNI_CreateJavaVM' in shared library");
608         return false;
609         }
611     String javaroot;
612     getJavaRoot(javaroot);
613     String cp;
614     populateClassPath(javaroot, cp);
615     String classpath = "-Djava.class.path=";
616     classpath.append(normalizePath(cp));
617     msg("Class path is: '%s'", classpath.c_str());
619     String libpath = "-Djava.library.path=";
620     libpath.append(javaroot);
621     libpath.append(DIR_SEPARATOR);
622     libpath.append("libm");
623     libpath = normalizePath(libpath);
624     msg("Lib path is: '%s'", libpath.c_str());
626     JavaVMInitArgs vm_args;
627     JavaVMOption options[4];
628     options[0].optionString    = (char *)classpath.c_str();
629     options[1].optionString    = (char *)libpath.c_str();
630     options[2].optionString    = (char *)"-verbose:jni";
631     options[3].optionString    = (char *)"vfprintf";
632     options[3].extraInfo       = (void *)vfprintfHook;
633     vm_args.version            = JNI_VERSION_1_4;
634     vm_args.options            = options;
635     vm_args.nOptions           = 4;
636     vm_args.ignoreUnrecognized = true;
638     if (createVM(&jvm, &env, &vm_args) < 0)
639         {
640         err("JNI_CreateJavaVM() failed");
641         return false;
642         }
644     //get jvm version
645     jint vers = env->GetVersion();
646     int versionMajor = (vers>>16) & 0xffff;
647     int versionMinor = (vers    ) & 0xffff;
648     msg("Loaded JVM version %d.%d", versionMajor, versionMinor);
650     if (!registerNatives("org/inkscape/cmn/ScriptRunner",
651              scriptRunnerMethods))
652         {
653         return false;
654         }
655     return true;
660 /**
661  *  This is a difficult method.  What we are doing is trying to
662  *  call a static method with a list of arguments.  Similar to 
663  *  a varargs call, we need to marshal the Values into their
664  *  Java equivalents and make the proper call.
665  *  
666  * @param type the return type of the method
667  * @param className the full (package / name) name of the java class
668  * @param methodName the name of the method being invoked
669  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
670  *    that describes the param and return types of the method.
671  * @param retval the return value of the java method
672  * @return true if the call was successful, else false.  This is not
673  *    the return value of the method.    
674  */    
675 bool JavaBinderyImpl::callStatic(int type,
676                         const String &className,
677                         const String &methodName,
678                         const String &signature,
679                         const std::vector<Value> &params,
680                         Value &retval)
682     jclass cls = env->FindClass(className.c_str());
683     if (!cls)
684         {
685         err("Could not find class '%s'", className.c_str());
686         return false;
687         }
688     jmethodID mid = env->GetStaticMethodID(cls,
689                 methodName.c_str(), signature.c_str());
690     if (!mid)
691         {
692         err("Could not find method '%s:%s/%s'", className.c_str(),
693                 methodName.c_str(), signature.c_str());
694         return false;
695         }
696     /**
697      * Assemble your parameters into a form usable by JNI
698      */
699     jvalue *jvals = new jvalue[params.size()];
700     for (unsigned int i=0 ; i<params.size() ; i++)
701         {
702         Value v = params[i];
703         switch (v.getType())
704             {
705             case Value::BIND_BOOLEAN:
706                 {
707                 jvals[i].z = (jboolean)v.getBoolean();
708                 break;
709                 }
710             case Value::BIND_INT:
711                 {
712                 jvals[i].i = (jint)v.getInt();
713                 break;
714                 }
715             case Value::BIND_DOUBLE:
716                 {
717                 jvals[i].d = (jdouble)v.getDouble();
718                 break;
719                 }
720             case Value::BIND_STRING:
721                 {
722                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
723                 break;
724                 }
725             default:
726                 {
727                 err("Unknown value type: %d", v.getType());
728                 return false;
729                 }
730             }
731         }
732     switch (type)
733         {
734         case Value::BIND_VOID:
735             {
736             env->CallStaticVoidMethodA(cls, mid, jvals);
737             break;
738             }
739         case Value::BIND_BOOLEAN:
740             {
741             env->CallStaticBooleanMethodA(cls, mid, jvals);
742             break;
743             }
744         case Value::BIND_INT:
745             {
746             env->CallStaticIntMethodA(cls, mid, jvals);
747             break;
748             }
749         case Value::BIND_DOUBLE:
750             {
751             env->CallStaticDoubleMethodA(cls, mid, jvals);
752             break;
753             }
754         case Value::BIND_STRING:
755             {
756             env->CallStaticObjectMethodA(cls, mid, jvals);
757             break;
758             }
759         default:
760             {
761             err("Unknown return type: %d", type);
762             return false;
763             }
764         }
765     delete jvals;
766     return true;
771 /**
772  * Convenience method to call the static void main(String argv[])
773  * method of a given class
774  * 
775  * @param className full name of the java class
776  * @return true if successful, else false   
777  */ 
778 bool JavaBinderyImpl::callMain(const String &className)
780     std::vector<Value> parms;
781     Value retval;
782     return callStatic(Value::BIND_VOID, className, "main",
783              "([Ljava/lang/String;)V", parms, retval);
787 /**
788  * Used to register an array of native methods for a named class
789  * 
790  * @param className the full name of the java class
791  * @param the method array
792  * @return true if successful, else false     
793  */ 
794 bool JavaBinderyImpl::registerNatives(const String &className,
795                            const JNINativeMethod *methods)
797     jclass cls = env->FindClass(className.c_str());
798     if (!cls)
799         {
800         err("Could not find class '%s'", className.c_str());
801         return false;
802         }
803     msg("registerNatives: class '%s' found", className.c_str());
804     
805     /**
806      * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
807      */
808         jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
809                   "()[Ljava/lang/reflect/Constructor;");
810         if (!mid)
811             {
812             err("Could not get reflect mid for 'getConstructors' : %s",
813              getException(env).c_str());
814                 return false;
815                 }
816         jobject res = env->CallObjectMethod(cls, mid);
817         if (!res)
818             {
819             err("Could not get constructors");
820                 return false;
821                 }
822         /**
823          * end hack
824          */             
825     jint nrMethods = 0;
826     for (const JNINativeMethod *m = methods ; m->name ; m++)
827         nrMethods++;
828     jint ret = env->RegisterNatives(cls, methods, nrMethods);
829     if (ret < 0)
830         {
831         err("Could not register %d native methods for '%s' : %s",
832                     nrMethods, className.c_str(), getException(env).c_str());
833         return false;
834         }
835     return true;
841 } // namespace Bind
842 } // namespace Inkscape
844 //########################################################################
845 //# E N D    O F    F I L E
846 //########################################################################