Code

a25be353eb5ccdfb241aa194934f6046ea888029
[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  */
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <string.h>
30 #include <jni.h>
32 #include <sys/types.h>
33 #include <dirent.h>
36 #ifdef __WIN32__
37 #include <windows.h>
38 #else
39 #include <dlfcn.h>
40 #include <errno.h>
41 #endif
43 #include "javabind.h"
44 #include "javabind-private.h"
45 #include <prefix.h>
46 #include <glib/gmessages.h>
52 namespace Inkscape
53 {
55 namespace Bind
56 {
59 //########################################################################
60 //# DEFINITIONS
61 //########################################################################
63 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
67 //########################################################################
68 //# UTILITY
69 //########################################################################
71 jint getInt(JNIEnv *env, jobject obj, const char *name)
72 {
73     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
74     return env->GetIntField(obj, fid);
75 }
77 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
78 {
79     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
80     env->SetIntField(obj, fid, val);
81 }
83 jlong getLong(JNIEnv *env, jobject obj, const char *name)
84 {
85     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
86     return env->GetLongField(obj, fid);
87 }
89 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
90 {
91     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
92     env->SetLongField(obj, fid, val);
93 }
95 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
96 {
97     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
98     return env->GetFloatField(obj, fid);
99 }
101 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
103     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
104     env->SetFloatField(obj, fid, val);
107 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
109     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
110     return env->GetDoubleField(obj, fid);
113 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
115     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
116     env->SetDoubleField(obj, fid, val);
119 String getString(JNIEnv *env, jobject obj, const char *name)
121     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
122     jstring jstr = (jstring)env->GetObjectField(obj, fid);
123     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
124     String str = chars;
125     env->ReleaseStringUTFChars(jstr, chars);
126     return str;
129 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
131     jstring jstr = env->NewStringUTF(val.c_str());
132     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
133     env->SetObjectField(obj, fid, jstr);
139 //########################################################################
140 //# CONSTRUCTOR/DESTRUCTOR
141 //########################################################################
143 static JavaBinderyImpl *_instance = NULL;
145 JavaBindery *JavaBindery::getInstance()
147     return JavaBinderyImpl::getInstance();
150 JavaBinderyImpl *JavaBinderyImpl::getInstance()
152     if (!_instance)
153         {
154         _instance = new JavaBinderyImpl();
155         }
156     return _instance;
159 JavaBinderyImpl::JavaBinderyImpl()
161     jvm  = NULL;
162     env  = NULL;
165 JavaBinderyImpl::~JavaBinderyImpl()
169 void err(const char *fmt, ...)
171 #if 0
172     va_list args;
173     fprintf(stderr, "JavaBinderyImpl err:");
174     va_start(args, fmt);
175     vfprintf(stderr, fmt, args);
176     va_end(args);
177     fprintf(stderr, "\n");
178 #else
179     va_list args;
180     g_warning("JavaBinderyImpl err:");
181     va_start(args, fmt);
182     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
183     va_end(args);
184     g_warning("\n");
185 #endif
188 void msg(const char *fmt, ...)
190 #if 0
191     va_list args;
192     fprintf(stdout, "JavaBinderyImpl:");
193     va_start(args, fmt);
194     vfprintf(stdout, fmt, args);
195     va_end(args);
196     fprintf(stdout, "\n");
197 #else
198     va_list args;
199     g_message("JavaBinderyImpl:");
200     va_start(args, fmt);
201     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
202     va_end(args);
203     g_message("\n");
204 #endif
207 bool JavaBinderyImpl::isLoaded()
209     return (jvm != (void *)0);
214 #ifdef __WIN32__
217 //########################################################################
218 //# W I N 3 2      S T Y L E
219 //########################################################################
222 #define DIR_SEPARATOR "\\"
223 #define PATH_SEPARATOR ";"
227 static bool getRegistryString(HKEY root, const char *keyName,
228                const char *valName, char *buf, int buflen)
230     HKEY key;
231     DWORD bufsiz  = buflen;
232     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
233     int ret = RegQueryValueEx(key, TEXT(valName),
234             NULL, NULL, (BYTE *)buf, &bufsiz);
235     if (ret != ERROR_SUCCESS)
236         {
237         err("Key '%s\\%s not found\n", keyName, valName);
238         return false;
239         }
240     RegCloseKey(key);
241     return true;
245 static CreateVMFunc getCreateVMFunc()
247     char verbuf[16];
248     char regpath[80];
249     strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
250     bool ret = getRegistryString(HKEY_LOCAL_MACHINE, 
251                      regpath, "CurrentVersion", verbuf, 15);
252     if (!ret)
253         {
254         err("JVM CurrentVersion not found in registry\n");
255         return NULL;
256         }
257     strcat(regpath, "\\");
258     strcat(regpath, verbuf);
259     //msg("reg path: %s\n", regpath);
260     char libname[80];
261     ret = getRegistryString(HKEY_LOCAL_MACHINE, 
262                      regpath, "RuntimeLib", libname, 79);
263     if (!ret)
264         {
265         err("Current JVM RuntimeLib not found in registry\n");
266         return NULL;
267         }
268     //msg("jvm path: %s\n", libname);
269     HMODULE lib = LoadLibrary(libname);
270     if (!lib)
271         {
272         err("Java VM not found at '%s'", libname);
273         return NULL;
274         }
275     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
276     if (!createVM)
277         {
278         err("Could not find 'JNI_CreateJavaVM' in shared library");
279         return NULL;
280         }
281     return createVM;
284 static void getJavaRoot(String &javaroot)
286     char exeName[80];
287     GetModuleFileName(NULL, exeName, 80);
288     char *slashPos = strrchr(exeName, '\\');
289     if (slashPos)
290         *slashPos = '\0';
291     javaroot = exeName;
292     javaroot.append("\\share\\java");
296 #else
299 //########################################################################
300 //# U N I X    S T Y L E
301 //########################################################################
304 #define DIR_SEPARATOR "/"
305 #define PATH_SEPARATOR ":"
308 /**
309  * Recursively descend into a directory looking for libjvm.so
310  */ 
311 static bool findJVMRecursive(const String &dirpath,
312                              std::vector<String> &results)
314     DIR *dir = opendir(dirpath.c_str());
315     if (!dir)
316         return false;
317     bool ret = false;
318     while (true)
319         {
320         struct dirent *de = readdir(dir);
321         if (!de)
322             break;
323         String fname = de->d_name;
324         if (fname == "." || fname == "..")
325             continue;
326         String path = dirpath;
327         path.push_back('/');
328         path.append(fname);
329         if (fname == "libjvm.so")
330             {
331             ret = true;
332             results.push_back(path);
333             continue;
334             }
335         struct stat finfo;
336         if (lstat(path.c_str(), &finfo)<0)
337             {
338             break;
339             }
340         if (finfo.st_mode & S_IFDIR)
341             {
342             ret |= findJVMRecursive(path, results);
343             }   
344         }
345     closedir(dir);
346     return ret;
350 static const char *commonJavaPaths[] =
352     "/usr/java",
353     "/usr/local/java",
354     "/usr/lib/jvm",
355     "/usr/local/lib/jvm",
356     NULL
357 };
359 /**
360  * Look for a Java VM (libjvm.so) in several Unix places
361  */ 
362 static bool findJVM(String &result)
364     std::vector<String> results;
365     int found = false;
367     /* Is there one specified by the user? */
368     const char *javaHome = getenv("JAVA_HOME");
369     if (javaHome && findJVMRecursive(javaHome, results))
370         found = true;
371     else for (const char **path = commonJavaPaths ; *path ; path++)
372         {
373         if (findJVMRecursive(*path, results))
374             {
375             found = true;
376             break;
377             }
378         }
379     if (!found)
380         {
381         return false;
382         }
383     if (results.size() == 0)
384         return false;
385     //Look first for a Client VM
386     for (unsigned int i=0 ; i<results.size() ; i++)
387         {
388         String s = results[i];
389         if (s.find("client") != s.npos)
390             {
391             result = s;
392             return true;
393             }
394         }
395     //else default to the first
396     result = results[0];
397     return true;
402 static CreateVMFunc getCreateVMFunc()
404     String libname;
405     if (!findJVM(libname))
406         {
407         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
408         return NULL;
409         }
410     void *lib = dlopen(libname.c_str(), RTLD_NOW);
411     if (!lib)
412         {
413         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
414         return NULL;
415         }
416     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
417     if (!createVM)
418         {
419         err("Could not find 'JNI_CreateJavaVM' in shared library");
420             return NULL;
421         }
422     return createVM;
426 static void getJavaRoot(String &javaroot)
428     javaroot = BR_DATADIR("/java");
431 #endif
437 static void populateClassPath(const String &javaroot,
438                               String &result)
440     String classdir = javaroot;
441     classdir.append(DIR_SEPARATOR);
442     classdir.append("classes");
444     String cp = classdir;
446     String libdir = javaroot;
447     libdir.append(DIR_SEPARATOR);
448     libdir.append("lib");
450     DIR *dir = opendir(libdir.c_str());
451     if (!dir)
452         {
453         result = cp;
454         return;
455         }
457     while (true)
458         {
459         struct dirent *de = readdir(dir);
460         if (!de)
461             break;
462         String fname = de->d_name;
463         if (fname == "." || fname == "..")
464             continue;
465         if (fname.size()<5) //x.jar
466             continue;
467         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
468             continue;
470         String path = libdir;
471         path.append(DIR_SEPARATOR);
472         path.append(fname);
474         cp.append(PATH_SEPARATOR);
475         cp.append(path);
476         }
477     closedir(dir);
478     
479     result = cp;
481     return;
485 static void stdOutWrite(jlong ptr, jint ch)
487     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
488     bind->stdOut(ch);
491 static void stdErrWrite(jlong ptr, jint ch)
493     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
494     bind->stdErr(ch);
498 static JNINativeMethod scriptRunnerMethods[] =
500 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
501 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
502 { NULL,  NULL, NULL }
503 };
505 bool JavaBinderyImpl::loadJVM()
507     if (jvm)
508         return true;
510     CreateVMFunc createVM = getCreateVMFunc();
511     if (!createVM)
512         {
513         err("Could not find 'JNI_CreateJavaVM' in shared library");
514         return false;
515         }
517     String javaroot;
518     getJavaRoot(javaroot);
519     String cp;
520     populateClassPath(javaroot, cp);
521     String classpath = "-Djava.class.path=";
522     classpath.append(cp);
523     msg("Class path is: '%s'", classpath.c_str());
525     String libpath = "-Djava.library.path=";
526     libpath.append(javaroot);
527     libpath.append(DIR_SEPARATOR);
528     libpath.append("libm");
529     msg("Lib path is: '%s'", libpath.c_str());
531     JavaVMInitArgs vm_args;
532     JavaVMOption options[2];
533     options[0].optionString    = (char *)classpath.c_str();
534     options[1].optionString    = (char *)libpath.c_str();
535     vm_args.version            = JNI_VERSION_1_2;
536     vm_args.options            = options;
537     vm_args.nOptions           = 2;
538     vm_args.ignoreUnrecognized = true;
540     if (createVM(&jvm, &env, &vm_args) < 0)
541         {
542         err("JNI_GetDefaultJavaVMInitArgs() failed");
543         return false;
544         }
546     if (!registerNatives("org/inkscape/cmn/ScriptRunner",
547              scriptRunnerMethods))
548         {
549         return false;
550         }
551     return true;
557 bool JavaBinderyImpl::callStatic(int type,
558                         const String &className,
559                         const String &methodName,
560                         const String &signature,
561                         const std::vector<Value> &params,
562                         Value &retval)
564     jclass cls = env->FindClass(className.c_str());
565     if (!cls)
566         {
567         err("Could not find class '%s'", className.c_str());
568         return false;
569         }
570     jmethodID mid = env->GetStaticMethodID(cls,
571                 methodName.c_str(), signature.c_str());
572     if (!mid)
573         {
574         err("Could not find method '%s:%s/%s'", className.c_str(),
575                 methodName.c_str(), signature.c_str());
576         return false;
577         }
578     /**
579      * Assemble your parameters into a form usable by JNI
580      */
581     jvalue *jvals = new jvalue[params.size()];
582     for (unsigned int i=0 ; i<params.size() ; i++)
583         {
584         Value v = params[i];
585         switch (v.getType())
586             {
587             case Value::BIND_BOOLEAN:
588                 {
589                 jvals[i].z = (jboolean)v.getBoolean();
590                 break;
591                 }
592             case Value::BIND_INT:
593                 {
594                 jvals[i].i = (jint)v.getInt();
595                 break;
596                 }
597             case Value::BIND_DOUBLE:
598                 {
599                 jvals[i].d = (jdouble)v.getDouble();
600                 break;
601                 }
602             case Value::BIND_STRING:
603                 {
604                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
605                 break;
606                 }
607             default:
608                 {
609                 err("Unknown value type: %d", v.getType());
610                 return false;
611                 }
612             }
613         }
614     switch (type)
615         {
616         case Value::BIND_VOID:
617             {
618             env->CallStaticVoidMethodA(cls, mid, jvals);
619             break;
620             }
621         case Value::BIND_BOOLEAN:
622             {
623             env->CallStaticBooleanMethodA(cls, mid, jvals);
624             break;
625             }
626         case Value::BIND_INT:
627             {
628             env->CallStaticIntMethodA(cls, mid, jvals);
629             break;
630             }
631         case Value::BIND_DOUBLE:
632             {
633             env->CallStaticDoubleMethodA(cls, mid, jvals);
634             break;
635             }
636         case Value::BIND_STRING:
637             {
638             env->CallStaticObjectMethodA(cls, mid, jvals);
639             break;
640             }
641         default:
642             {
643             err("Unknown return type: %d", type);
644             return false;
645             }
646         }
647     delete jvals;
648     return true;
654 bool JavaBinderyImpl::callMain(const String &className)
656     std::vector<Value> parms;
657     Value retval;
658     return callStatic(Value::BIND_VOID, className, "main",
659              "([Ljava/lang/String;)V", parms, retval);
664 bool JavaBinderyImpl::registerNatives(const String &className,
665                            const JNINativeMethod *methods)
667     jclass cls = env->FindClass(className.c_str());
668     if (!cls)
669         {
670         err("Could not find class '%s'", className.c_str());
671         return false;
672         }
673     int nrMethods = 0;
674     for (const JNINativeMethod *m = methods ; m->name ; m++)
675         nrMethods++;
676     if (env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods) < 0)
677         {
678         err("Could not register natives");
679         return false;
680         }
681     return true;
687 } // namespace Bind
688 } // namespace Inkscape
690 //########################################################################
691 //# E N D    O F    F I L E
692 //########################################################################