Code

Changed to use INKSCAPE_JAVADIR
[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 #include "javabind.h"
47 #include "javabind-private.h"
48 #include <path-prefix.h>
49 #include <prefix.h>
50 #include <glib/gmessages.h>
56 namespace Inkscape
57 {
59 namespace Bind
60 {
63 //########################################################################
64 //# DEFINITIONS
65 //########################################################################
67 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
71 //########################################################################
72 //# UTILITY
73 //########################################################################
75 jint getInt(JNIEnv *env, jobject obj, const char *name)
76 {
77     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
78     return env->GetIntField(obj, fid);
79 }
81 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
82 {
83     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
84     env->SetIntField(obj, fid, val);
85 }
87 jlong getLong(JNIEnv *env, jobject obj, const char *name)
88 {
89     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
90     return env->GetLongField(obj, fid);
91 }
93 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
94 {
95     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
96     env->SetLongField(obj, fid, val);
97 }
99 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
101     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
102     return env->GetFloatField(obj, fid);
105 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
107     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
108     env->SetFloatField(obj, fid, val);
111 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
113     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
114     return env->GetDoubleField(obj, fid);
117 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
119     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
120     env->SetDoubleField(obj, fid, val);
123 String getString(JNIEnv *env, jobject obj, const char *name)
125     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
126     jstring jstr = (jstring)env->GetObjectField(obj, fid);
127     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
128     String str = chars;
129     env->ReleaseStringUTFChars(jstr, chars);
130     return str;
133 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
135     jstring jstr = env->NewStringUTF(val.c_str());
136     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
137     env->SetObjectField(obj, fid, jstr);
143 //########################################################################
144 //# CONSTRUCTOR/DESTRUCTOR
145 //########################################################################
147 static JavaBinderyImpl *_instance = NULL;
149 JavaBindery *JavaBindery::getInstance()
151     return JavaBinderyImpl::getInstance();
154 JavaBinderyImpl *JavaBinderyImpl::getInstance()
156     if (!_instance)
157         {
158         _instance = new JavaBinderyImpl();
159         }
160     return _instance;
163 JavaBinderyImpl::JavaBinderyImpl()
165     jvm  = NULL;
166     env  = NULL;
169 JavaBinderyImpl::~JavaBinderyImpl()
173 void err(const char *fmt, ...)
175 #if 0
176     va_list args;
177     fprintf(stderr, "JavaBinderyImpl err:");
178     va_start(args, fmt);
179     vfprintf(stderr, fmt, args);
180     va_end(args);
181     fprintf(stderr, "\n");
182 #else
183     va_list args;
184     g_warning("JavaBinderyImpl err:");
185     va_start(args, fmt);
186     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
187     va_end(args);
188     g_warning("\n");
189 #endif
192 void msg(const char *fmt, ...)
194 #if 0
195     va_list args;
196     fprintf(stdout, "JavaBinderyImpl:");
197     va_start(args, fmt);
198     vfprintf(stdout, fmt, args);
199     va_end(args);
200     fprintf(stdout, "\n");
201 #else
202     va_list args;
203     g_message("JavaBinderyImpl:");
204     va_start(args, fmt);
205     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
206     va_end(args);
207     g_message("\n");
208 #endif
211 bool JavaBinderyImpl::isLoaded()
213     return (jvm != (void *)0);
218 #ifdef __WIN32__
221 //########################################################################
222 //# W I N 3 2      S T Y L E
223 //########################################################################
226 #define DIR_SEPARATOR "\\"
227 #define PATH_SEPARATOR ";"
231 static bool getRegistryString(HKEY root, const char *keyName,
232                const char *valName, char *buf, int buflen)
234     HKEY key;
235     DWORD bufsiz  = buflen;
236     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
237     int ret = RegQueryValueEx(key, TEXT(valName),
238             NULL, NULL, (BYTE *)buf, &bufsiz);
239     if (ret != ERROR_SUCCESS)
240         {
241         err("Key '%s\\%s not found\n", keyName, valName);
242         return false;
243         }
244     RegCloseKey(key);
245     return true;
249 static CreateVMFunc getCreateVMFunc()
251     char verbuf[16];
252     char regpath[80];
253     strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
254     bool ret = getRegistryString(HKEY_LOCAL_MACHINE, 
255                      regpath, "CurrentVersion", verbuf, 15);
256     if (!ret)
257         {
258         err("JVM CurrentVersion not found in registry\n");
259         return NULL;
260         }
261     strcat(regpath, "\\");
262     strcat(regpath, verbuf);
263     //msg("reg path: %s\n", regpath);
264     char libname[80];
265     ret = getRegistryString(HKEY_LOCAL_MACHINE, 
266                      regpath, "RuntimeLib", libname, 79);
267     if (!ret)
268         {
269         err("Current JVM RuntimeLib not found in registry\n");
270         return NULL;
271         }
272     //msg("jvm path: %s\n", libname);
273     HMODULE lib = LoadLibrary(libname);
274     if (!lib)
275         {
276         err("Java VM not found at '%s'", libname);
277         return NULL;
278         }
279     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
280     if (!createVM)
281         {
282         err("Could not find 'JNI_CreateJavaVM' in shared library");
283         return NULL;
284         }
285     return createVM;
288 static void getJavaRoot(String &javaroot)
290     char exeName[80];
291     GetModuleFileName(NULL, exeName, 80);
292     char *slashPos = strrchr(exeName, '\\');
293     if (slashPos)
294         *slashPos = '\0';
295     javaroot = exeName;
296     javaroot.append("\\");
297     javaroot.append(INKSCAPE_JAVADIR);
301 #else
304 //########################################################################
305 //# U N I X    S T Y L E
306 //########################################################################
309 #define DIR_SEPARATOR "/"
310 #define PATH_SEPARATOR ":"
313 /**
314  * Recursively descend into a directory looking for libjvm.so
315  */ 
316 static bool findJVMRecursive(const String &dirpath,
317                              std::vector<String> &results)
319     DIR *dir = opendir(dirpath.c_str());
320     if (!dir)
321         return false;
322     bool ret = false;
323     while (true)
324         {
325         struct dirent *de = readdir(dir);
326         if (!de)
327             break;
328         String fname = de->d_name;
329         if (fname == "." || fname == "..")
330             continue;
331         String path = dirpath;
332         path.push_back('/');
333         path.append(fname);
334         if (fname == "libjvm.so")
335             {
336             ret = true;
337             results.push_back(path);
338             continue;
339             }
340         struct stat finfo;
341         if (lstat(path.c_str(), &finfo)<0)
342             {
343             break;
344             }
345         if (finfo.st_mode & S_IFDIR)
346             {
347             ret |= findJVMRecursive(path, results);
348             }   
349         }
350     closedir(dir);
351     return ret;
355 static const char *commonJavaPaths[] =
357     "/usr/java",
358     "/usr/local/java",
359     "/usr/lib/jvm",
360     "/usr/local/lib/jvm",
361     NULL
362 };
364 /**
365  * Look for a Java VM (libjvm.so) in several Unix places
366  */ 
367 static bool findJVM(String &result)
369     std::vector<String> results;
370     int found = false;
372     /* Is there one specified by the user? */
373     const char *javaHome = getenv("JAVA_HOME");
374     if (javaHome && findJVMRecursive(javaHome, results))
375         found = true;
376     else for (const char **path = commonJavaPaths ; *path ; path++)
377         {
378         if (findJVMRecursive(*path, results))
379             {
380             found = true;
381             break;
382             }
383         }
384     if (!found)
385         {
386         return false;
387         }
388     if (results.size() == 0)
389         return false;
390     //Look first for a Client VM
391     for (unsigned int i=0 ; i<results.size() ; i++)
392         {
393         String s = results[i];
394         if (s.find("client") != s.npos)
395             {
396             result = s;
397             return true;
398             }
399         }
400     //else default to the first
401     result = results[0];
402     return true;
407 static CreateVMFunc getCreateVMFunc()
409     String libname;
410     if (!findJVM(libname))
411         {
412         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
413         return NULL;
414         }
415     void *lib = dlopen(libname.c_str(), RTLD_NOW);
416     if (!lib)
417         {
418         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
419         return NULL;
420         }
421     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
422     if (!createVM)
423         {
424         err("Could not find 'JNI_CreateJavaVM' in shared library");
425             return NULL;
426         }
427     return createVM;
431 static void getJavaRoot(String &javaroot)
433     javaroot = INKSCAPE_JAVADIR;
436 #endif
442 static void populateClassPath(const String &javaroot,
443                               String &result)
445     String classdir = javaroot;
446     classdir.append(DIR_SEPARATOR);
447     classdir.append("classes");
449     String cp = classdir;
451     String libdir = javaroot;
452     libdir.append(DIR_SEPARATOR);
453     libdir.append("lib");
455     DIR *dir = opendir(libdir.c_str());
456     if (!dir)
457         {
458         result = cp;
459         return;
460         }
462     while (true)
463         {
464         struct dirent *de = readdir(dir);
465         if (!de)
466             break;
467         String fname = de->d_name;
468         if (fname == "." || fname == "..")
469             continue;
470         if (fname.size()<5) //x.jar
471             continue;
472         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
473             continue;
475         String path = libdir;
476         path.append(DIR_SEPARATOR);
477         path.append(fname);
479         cp.append(PATH_SEPARATOR);
480         cp.append(path);
481         }
482     closedir(dir);
483     
484     result = cp;
486     return;
490 static void stdOutWrite(jlong ptr, jint ch)
492     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
493     bind->stdOut(ch);
496 static void stdErrWrite(jlong ptr, jint ch)
498     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
499     bind->stdErr(ch);
503 static JNINativeMethod scriptRunnerMethods[] =
505 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
506 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
507 { NULL,  NULL, NULL }
508 };
510 bool JavaBinderyImpl::loadJVM()
512     if (jvm)
513         return true;
515     CreateVMFunc createVM = getCreateVMFunc();
516     if (!createVM)
517         {
518         err("Could not find 'JNI_CreateJavaVM' in shared library");
519         return false;
520         }
522     String javaroot;
523     getJavaRoot(javaroot);
524     String cp;
525     populateClassPath(javaroot, cp);
526     String classpath = "-Djava.class.path=";
527     classpath.append(cp);
528     msg("Class path is: '%s'", classpath.c_str());
530     String libpath = "-Djava.library.path=";
531     libpath.append(javaroot);
532     libpath.append(DIR_SEPARATOR);
533     libpath.append("libm");
534     msg("Lib path is: '%s'", libpath.c_str());
536     JavaVMInitArgs vm_args;
537     JavaVMOption options[2];
538     options[0].optionString    = (char *)classpath.c_str();
539     options[1].optionString    = (char *)libpath.c_str();
540     vm_args.version            = JNI_VERSION_1_2;
541     vm_args.options            = options;
542     vm_args.nOptions           = 2;
543     vm_args.ignoreUnrecognized = true;
545     if (createVM(&jvm, &env, &vm_args) < 0)
546         {
547         err("JNI_GetDefaultJavaVMInitArgs() failed");
548         return false;
549         }
551     if (!registerNatives("org/inkscape/cmn/ScriptRunner",
552              scriptRunnerMethods))
553         {
554         return false;
555         }
556     return true;
562 bool JavaBinderyImpl::callStatic(int type,
563                         const String &className,
564                         const String &methodName,
565                         const String &signature,
566                         const std::vector<Value> &params,
567                         Value &retval)
569     jclass cls = env->FindClass(className.c_str());
570     if (!cls)
571         {
572         err("Could not find class '%s'", className.c_str());
573         return false;
574         }
575     jmethodID mid = env->GetStaticMethodID(cls,
576                 methodName.c_str(), signature.c_str());
577     if (!mid)
578         {
579         err("Could not find method '%s:%s/%s'", className.c_str(),
580                 methodName.c_str(), signature.c_str());
581         return false;
582         }
583     /**
584      * Assemble your parameters into a form usable by JNI
585      */
586     jvalue *jvals = new jvalue[params.size()];
587     for (unsigned int i=0 ; i<params.size() ; i++)
588         {
589         Value v = params[i];
590         switch (v.getType())
591             {
592             case Value::BIND_BOOLEAN:
593                 {
594                 jvals[i].z = (jboolean)v.getBoolean();
595                 break;
596                 }
597             case Value::BIND_INT:
598                 {
599                 jvals[i].i = (jint)v.getInt();
600                 break;
601                 }
602             case Value::BIND_DOUBLE:
603                 {
604                 jvals[i].d = (jdouble)v.getDouble();
605                 break;
606                 }
607             case Value::BIND_STRING:
608                 {
609                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
610                 break;
611                 }
612             default:
613                 {
614                 err("Unknown value type: %d", v.getType());
615                 return false;
616                 }
617             }
618         }
619     switch (type)
620         {
621         case Value::BIND_VOID:
622             {
623             env->CallStaticVoidMethodA(cls, mid, jvals);
624             break;
625             }
626         case Value::BIND_BOOLEAN:
627             {
628             env->CallStaticBooleanMethodA(cls, mid, jvals);
629             break;
630             }
631         case Value::BIND_INT:
632             {
633             env->CallStaticIntMethodA(cls, mid, jvals);
634             break;
635             }
636         case Value::BIND_DOUBLE:
637             {
638             env->CallStaticDoubleMethodA(cls, mid, jvals);
639             break;
640             }
641         case Value::BIND_STRING:
642             {
643             env->CallStaticObjectMethodA(cls, mid, jvals);
644             break;
645             }
646         default:
647             {
648             err("Unknown return type: %d", type);
649             return false;
650             }
651         }
652     delete jvals;
653     return true;
659 bool JavaBinderyImpl::callMain(const String &className)
661     std::vector<Value> parms;
662     Value retval;
663     return callStatic(Value::BIND_VOID, className, "main",
664              "([Ljava/lang/String;)V", parms, retval);
669 bool JavaBinderyImpl::registerNatives(const String &className,
670                            const JNINativeMethod *methods)
672     jclass cls = env->FindClass(className.c_str());
673     if (!cls)
674         {
675         err("Could not find class '%s'", className.c_str());
676         return false;
677         }
678     int nrMethods = 0;
679     for (const JNINativeMethod *m = methods ; m->name ; m++)
680         nrMethods++;
681     if (env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods) < 0)
682         {
683         err("Could not register natives");
684         return false;
685         }
686     return true;
692 } // namespace Bind
693 } // namespace Inkscape
695 //########################################################################
696 //# E N D    O F    F I L E
697 //########################################################################