Code

bug fixes
[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         {
106         env->ExceptionClear();
107                 }
108         return buf;
111 jint getInt(JNIEnv *env, jobject obj, const char *name)
113     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
114     return env->GetIntField(obj, fid);
117 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
119     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
120     env->SetIntField(obj, fid, val);
123 jlong getLong(JNIEnv *env, jobject obj, const char *name)
125     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
126     return env->GetLongField(obj, fid);
129 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
131     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
132     env->SetLongField(obj, fid, val);
135 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
137     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
138     return env->GetFloatField(obj, fid);
141 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
143     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
144     env->SetFloatField(obj, fid, val);
147 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
149     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
150     return env->GetDoubleField(obj, fid);
153 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
155     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
156     env->SetDoubleField(obj, fid, val);
159 String getString(JNIEnv *env, jobject obj, const char *name)
161     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
162     jstring jstr = (jstring)env->GetObjectField(obj, fid);
163     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
164     String str = chars;
165     env->ReleaseStringUTFChars(jstr, chars);
166     return str;
169 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
171     jstring jstr = env->NewStringUTF(val.c_str());
172     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
173     env->SetObjectField(obj, fid, jstr);
179 //########################################################################
180 //# CONSTRUCTOR/DESTRUCTOR
181 //########################################################################
183 static JavaBinderyImpl *_instance = NULL;
185 JavaBindery *JavaBindery::getInstance()
187     return JavaBinderyImpl::getInstance();
190 JavaBinderyImpl *JavaBinderyImpl::getInstance()
192     if (!_instance)
193         {
194         _instance = new JavaBinderyImpl();
195         }
196     return _instance;
199 JavaBinderyImpl::JavaBinderyImpl()
201     jvm  = NULL;
202     env  = NULL;
205 JavaBinderyImpl::~JavaBinderyImpl()
210 //########################################################################
211 //# MESSAGES
212 //########################################################################
214 void err(const char *fmt, ...)
216 #if 0
217     va_list args;
218     fprintf(stderr, "JavaBinderyImpl err:");
219     va_start(args, fmt);
220     vfprintf(stderr, fmt, args);
221     va_end(args);
222     fprintf(stderr, "\n");
223 #else
224     va_list args;
225     g_warning("JavaBinderyImpl err:");
226     va_start(args, fmt);
227     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
228     va_end(args);
229     g_warning("\n");
230 #endif
233 void msg(const char *fmt, ...)
235 #if 0
236     va_list args;
237     fprintf(stdout, "JavaBinderyImpl:");
238     va_start(args, fmt);
239     vfprintf(stdout, fmt, args);
240     va_end(args);
241     fprintf(stdout, "\n");
242 #else
243     va_list args;
244     g_message("JavaBinderyImpl:");
245     va_start(args, fmt);
246     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
247     va_end(args);
248     g_message("\n");
249 #endif
254 //########################################################################
255 //# W I N 3 2      S T Y L E
256 //########################################################################
257 #ifdef __WIN32__
260 #define DIR_SEPARATOR "\\"
261 #define PATH_SEPARATOR ";"
265 static bool getRegistryString(HKEY root, const char *keyName,
266                const char *valName, char *buf, int buflen)
268     HKEY key;
269     DWORD bufsiz  = buflen;
270     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
271     int ret = RegQueryValueEx(key, TEXT(valName),
272             NULL, NULL, (BYTE *)buf, &bufsiz);
273     if (ret != ERROR_SUCCESS)
274         {
275         err("Key '%s\\%s not found\n", keyName, valName);
276         return false;
277         }
278     RegCloseKey(key);
279     return true;
283 static CreateVMFunc getCreateVMFunc()
285     char verbuf[16];
286     char regpath[80];
287     strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
288     bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
289                      regpath, "CurrentVersion", verbuf, 15);
290     if (!ret)
291         {
292         err("JVM CurrentVersion not found in registry\n");
293         return NULL;
294         }
295     strcat(regpath, "\\");
296     strcat(regpath, verbuf);
297     //msg("reg path: %s\n", regpath);
298     char libname[80];
299     ret = getRegistryString(HKEY_LOCAL_MACHINE,
300                      regpath, "RuntimeLib", libname, 79);
301     if (!ret)
302         {
303         err("Current JVM RuntimeLib not found in registry\n");
304         return NULL;
305         }
306     //msg("jvm path: %s\n", libname);
307     HMODULE lib = LoadLibrary(libname);
308     if (!lib)
309         {
310         err("Java VM not found at '%s'", libname);
311         return NULL;
312         }
313     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
314     if (!createVM)
315         {
316         err("Could not find 'JNI_CreateJavaVM' in shared library");
317         return NULL;
318         }
319     return createVM;
322 static void getJavaRoot(String &javaroot)
324     char exeName[80];
325     GetModuleFileName(NULL, exeName, 80);
326     char *slashPos = strrchr(exeName, '\\');
327     if (slashPos)
328         *slashPos = '\0';
329     javaroot = exeName;
330     javaroot.append("\\");
331     javaroot.append(INKSCAPE_JAVADIR);
337 //########################################################################
338 //# U N I X    S T Y L E
339 //########################################################################
340 #else /* !__WIN32__ */
343 #define DIR_SEPARATOR "/"
344 #define PATH_SEPARATOR ":"
347 /**
348  * Recursively descend into a directory looking for libjvm.so
349  */
350 static bool findJVMRecursive(const String &dirpath,
351                              std::vector<String> &results)
353     DIR *dir = opendir(dirpath.c_str());
354     if (!dir)
355         return false;
356     bool ret = false;
357     while (true)
358         {
359         struct dirent *de = readdir(dir);
360         if (!de)
361             break;
362         String fname = de->d_name;
363         if (fname == "." || fname == "..")
364             continue;
365         String path = dirpath;
366         path.push_back('/');
367         path.append(fname);
368         if (fname == "libjvm.so")
369             {
370             ret = true;
371             results.push_back(path);
372             continue;
373             }
374         struct stat finfo;
375         if (lstat(path.c_str(), &finfo)<0)
376             {
377             break;
378             }
379         if (finfo.st_mode & S_IFDIR)
380             {
381             ret |= findJVMRecursive(path, results);
382             }
383         }
384     closedir(dir);
385     return ret;
389 static const char *commonJavaPaths[] =
391     "/usr/java",
392     "/usr/local/java",
393     "/usr/lib/jvm",
394     "/usr/local/lib/jvm",
395     NULL
396 };
398 /**
399  * Look for a Java VM (libjvm.so) in several Unix places
400  */
401 static bool findJVM(String &result)
403     std::vector<String> results;
404     int found = false;
406     /* Is there one specified by the user? */
407     const char *javaHome = getenv("JAVA_HOME");
408     if (javaHome && findJVMRecursive(javaHome, results))
409         found = true;
410     else for (const char **path = commonJavaPaths ; *path ; path++)
411         {
412         if (findJVMRecursive(*path, results))
413             {
414             found = true;
415             break;
416             }
417         }
418     if (!found)
419         {
420         return false;
421         }
422     if (results.size() == 0)
423         return false;
424     //Look first for a Client VM
425     for (unsigned int i=0 ; i<results.size() ; i++)
426         {
427         String s = results[i];
428         if (s.find("client") != s.npos)
429             {
430             result = s;
431             return true;
432             }
433         }
434     //else default to the first
435     result = results[0];
436     return true;
441 static CreateVMFunc getCreateVMFunc()
443     String libname;
444     if (!findJVM(libname))
445         {
446         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
447         return NULL;
448         }
449     void *lib = dlopen(libname.c_str(), RTLD_NOW);
450     if (!lib)
451         {
452         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
453         return NULL;
454         }
455     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
456     if (!createVM)
457         {
458         err("Could not find 'JNI_CreateJavaVM' in shared library");
459             return NULL;
460         }
461     return createVM;
465 static void getJavaRoot(String &javaroot)
467     javaroot = INKSCAPE_JAVADIR;
470 #endif /* !__WIN32__ */
473 //########################################################################
474 //# COMMON
475 //########################################################################
478 bool JavaBinderyImpl::isLoaded()
480     return (jvm != (void *)0);
485 /**
486  * This will set up the classpath for the launched VM.
487  * We will add two things:
488  *   1.  INKSCAPE_JAVADIR/classes -- path to loose classes
489  *   2.  A concatenation of all jar files in INKSCAPE_JAVADIR/lib
490  *
491  * This will allow people to add classes and jars to the JVM without
492  * needing to state them explicitly.
493  * 
494  * @param javaroot.  Should be INKSCAPE_JAVADIR
495  * @param result a string buffer to hold the result of this method   
496  */        
497 static void populateClassPath(const String &javaroot,
498                               String &result)
500     String classdir = javaroot;
501     classdir.append(DIR_SEPARATOR);
502     classdir.append("classes");
504     String cp = classdir;
506     String libdir = javaroot;
507     libdir.append(DIR_SEPARATOR);
508     libdir.append("lib");
510     DIR *dir = opendir(libdir.c_str());
511     if (!dir)
512         {
513         result = cp;
514         return;
515         }
517     while (true)
518         {
519         struct dirent *de = readdir(dir);
520         if (!de)
521             break;
522         String fname = de->d_name;
523         if (fname == "." || fname == "..")
524             continue;
525         if (fname.size()<5) //x.jar
526             continue;
527         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
528             continue;
530         String path = libdir;
531         path.append(DIR_SEPARATOR);
532         path.append(fname);
534         cp.append(PATH_SEPARATOR);
535         cp.append(path);
536         }
537     closedir(dir);
538     
539     result = cp;
544 //========================================================================
545 // Native methods
546 //========================================================================
547 /**
548  * These methods are used to allow the ScriptRunner class to
549  * redirect its stderr and stdout streams to here, to be caught
550  * by two string buffers.  We can then use those buffers how we
551  * want.  These native methods are only those needed for running
552  * a script.  For the main C++/Java bindings, see dobinding.cpp 
553  */    
554 void JNICALL stdOutWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
556     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
557     bind->stdOut(ch);
560 void JNICALL stdErrWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
562     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
563     bind->stdErr(ch);
567 static JNINativeMethod scriptRunnerMethods[] =
569 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
570 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
571 { NULL,  NULL, NULL }
572 };
573 //========================================================================
574 // End native methods
575 //========================================================================
578 /**
579  * This is used to grab output from the VM itself. See 'options' below.
580  */ 
581 static int JNICALL vfprintfHook(FILE* f, const char *fmt, va_list args)
583     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
587 /**
588  * This is the most important part of this class.  Here we
589  * attempt to find, load, and initialize a java (or mlvm?) virtual
590  * machine.
591  * 
592  * @return true if successful, else false    
593  */ 
594 bool JavaBinderyImpl::loadJVM()
596     if (jvm)
597         return true;
599     CreateVMFunc createVM = getCreateVMFunc();
600     if (!createVM)
601         {
602         err("Could not find 'JNI_CreateJavaVM' in shared library");
603         return false;
604         }
606     String javaroot;
607     getJavaRoot(javaroot);
608     String cp;
609     populateClassPath(javaroot, cp);
610     String classpath = "-Djava.class.path=";
611     classpath.append(normalizePath(cp));
612     msg("Class path is: '%s'", classpath.c_str());
614     String libpath = "-Djava.library.path=";
615     libpath.append(javaroot);
616     libpath.append(DIR_SEPARATOR);
617     libpath.append("libm");
618     libpath = normalizePath(libpath);
619     msg("Lib path is: '%s'", libpath.c_str());
621     JavaVMInitArgs vm_args;
622     JavaVMOption options[4];
623     options[0].optionString    = (char *)classpath.c_str();
624     options[1].optionString    = (char *)libpath.c_str();
625     options[2].optionString    = "-verbose:jni";
626     options[3].optionString    = "vfprintf";
627     options[3].extraInfo       = (void *)vfprintfHook;
628     vm_args.version            = JNI_VERSION_1_4;
629     vm_args.options            = options;
630     vm_args.nOptions           = 4;
631     vm_args.ignoreUnrecognized = true;
633     if (createVM(&jvm, &env, &vm_args) < 0)
634         {
635         err("JNI_GetDefaultJavaVMInitArgs() failed");
636         return false;
637         }
639     if (!registerNatives("org/inkscape/cmn/ScriptRunner",
640              scriptRunnerMethods))
641         {
642         return false;
643         }
644     return true;
649 /**
650  *  This is a difficult method.  What we are doing is trying to
651  *  call a static method with a list of arguments.  Similar to 
652  *  a varargs call, we need to marshal the Values into their
653  *  Java equivalents and make the proper call.
654  *  
655  * @param type the return type of the method
656  * @param className the full (package / name) name of the java class
657  * @param methodName the name of the method being invoked
658  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
659  *    that describes the param and return types of the method.
660  * @param retval the return value of the java method
661  * @return true if the call was successful, else false.  This is not
662  *    the return value of the method.    
663  */    
664 bool JavaBinderyImpl::callStatic(int type,
665                         const String &className,
666                         const String &methodName,
667                         const String &signature,
668                         const std::vector<Value> &params,
669                         Value &retval)
671     jclass cls = env->FindClass(className.c_str());
672     if (!cls)
673         {
674         err("Could not find class '%s'", className.c_str());
675         return false;
676         }
677     jmethodID mid = env->GetStaticMethodID(cls,
678                 methodName.c_str(), signature.c_str());
679     if (!mid)
680         {
681         err("Could not find method '%s:%s/%s'", className.c_str(),
682                 methodName.c_str(), signature.c_str());
683         return false;
684         }
685     /**
686      * Assemble your parameters into a form usable by JNI
687      */
688     jvalue *jvals = new jvalue[params.size()];
689     for (unsigned int i=0 ; i<params.size() ; i++)
690         {
691         Value v = params[i];
692         switch (v.getType())
693             {
694             case Value::BIND_BOOLEAN:
695                 {
696                 jvals[i].z = (jboolean)v.getBoolean();
697                 break;
698                 }
699             case Value::BIND_INT:
700                 {
701                 jvals[i].i = (jint)v.getInt();
702                 break;
703                 }
704             case Value::BIND_DOUBLE:
705                 {
706                 jvals[i].d = (jdouble)v.getDouble();
707                 break;
708                 }
709             case Value::BIND_STRING:
710                 {
711                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
712                 break;
713                 }
714             default:
715                 {
716                 err("Unknown value type: %d", v.getType());
717                 return false;
718                 }
719             }
720         }
721     switch (type)
722         {
723         case Value::BIND_VOID:
724             {
725             env->CallStaticVoidMethodA(cls, mid, jvals);
726             break;
727             }
728         case Value::BIND_BOOLEAN:
729             {
730             env->CallStaticBooleanMethodA(cls, mid, jvals);
731             break;
732             }
733         case Value::BIND_INT:
734             {
735             env->CallStaticIntMethodA(cls, mid, jvals);
736             break;
737             }
738         case Value::BIND_DOUBLE:
739             {
740             env->CallStaticDoubleMethodA(cls, mid, jvals);
741             break;
742             }
743         case Value::BIND_STRING:
744             {
745             env->CallStaticObjectMethodA(cls, mid, jvals);
746             break;
747             }
748         default:
749             {
750             err("Unknown return type: %d", type);
751             return false;
752             }
753         }
754     delete jvals;
755     return true;
760 /**
761  * Convenience method to call the static void main(String argv[])
762  * method of a given class
763  * 
764  * @param className full name of the java class
765  * @return true if successful, else false   
766  */ 
767 bool JavaBinderyImpl::callMain(const String &className)
769     std::vector<Value> parms;
770     Value retval;
771     return callStatic(Value::BIND_VOID, className, "main",
772              "([Ljava/lang/String;)V", parms, retval);
776 /**
777  * Used to register an array of native methods for a named class
778  * 
779  * @param className the full name of the java class
780  * @param the method array
781  * @return true if successful, else false     
782  */ 
783 bool JavaBinderyImpl::registerNatives(const String &className,
784                            const JNINativeMethod *methods)
786     jclass cls = env->FindClass(className.c_str());
787     if (!cls)
788         {
789         err("Could not find class '%s'", className.c_str());
790         return false;
791         }
792     /**
793      * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
794      */
795         jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
796                   "()[Ljava/lang/reflect/Constructor;");
797         if (!mid)
798             {
799             err("Could not get reflect mid for 'getConstructors'");
800                 return false;
801                 }   
802         env->CallObjectMethod(cls, mid);
803         /**
804          * end hack
805          */             
806     jint nrMethods = 0;
807     for (const JNINativeMethod *m = methods ; m->name ; m++)
808         nrMethods++;
809     jint ret = env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods);
810     if (ret < 0)
811         {
812         err("Could not register %d native methods for '%s'",
813                                     nrMethods, className.c_str());
814                 if (env->ExceptionCheck())
815                     {
816                         env->ExceptionDescribe();
817                         env->ExceptionClear();
818                         }
819         return false;
820         }
821     return true;
827 } // namespace Bind
828 } // namespace Inkscape
830 //########################################################################
831 //# E N D    O F    F I L E
832 //########################################################################