Code

Added comments
[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 jint getInt(JNIEnv *env, jobject obj, const char *name)
83 {
84     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
85     return env->GetIntField(obj, fid);
86 }
88 void setInt(JNIEnv *env, jobject obj, const char *name, jint val)
89 {
90     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
91     env->SetIntField(obj, fid, val);
92 }
94 jlong getLong(JNIEnv *env, jobject obj, const char *name)
95 {
96     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
97     return env->GetLongField(obj, fid);
98 }
100 void setLong(JNIEnv *env, jobject obj, const char *name, jlong val)
102     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
103     env->SetLongField(obj, fid, val);
106 jfloat getFloat(JNIEnv *env, jobject obj, const char *name)
108     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
109     return env->GetFloatField(obj, fid);
112 void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
114     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
115     env->SetFloatField(obj, fid, val);
118 jdouble getDouble(JNIEnv *env, jobject obj, const char *name)
120     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
121     return env->GetDoubleField(obj, fid);
124 void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
126     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
127     env->SetDoubleField(obj, fid, val);
130 String getString(JNIEnv *env, jobject obj, const char *name)
132     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
133     jstring jstr = (jstring)env->GetObjectField(obj, fid);
134     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
135     String str = chars;
136     env->ReleaseStringUTFChars(jstr, chars);
137     return str;
140 void setString(JNIEnv *env, jobject obj, const char *name, const String &val)
142     jstring jstr = env->NewStringUTF(val.c_str());
143     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
144     env->SetObjectField(obj, fid, jstr);
150 //########################################################################
151 //# CONSTRUCTOR/DESTRUCTOR
152 //########################################################################
154 static JavaBinderyImpl *_instance = NULL;
156 JavaBindery *JavaBindery::getInstance()
158     return JavaBinderyImpl::getInstance();
161 JavaBinderyImpl *JavaBinderyImpl::getInstance()
163     if (!_instance)
164         {
165         _instance = new JavaBinderyImpl();
166         }
167     return _instance;
170 JavaBinderyImpl::JavaBinderyImpl()
172     jvm  = NULL;
173     env  = NULL;
176 JavaBinderyImpl::~JavaBinderyImpl()
181 //########################################################################
182 //# MESSAGES
183 //########################################################################
185 void err(const char *fmt, ...)
187 #if 0
188     va_list args;
189     fprintf(stderr, "JavaBinderyImpl err:");
190     va_start(args, fmt);
191     vfprintf(stderr, fmt, args);
192     va_end(args);
193     fprintf(stderr, "\n");
194 #else
195     va_list args;
196     g_warning("JavaBinderyImpl err:");
197     va_start(args, fmt);
198     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
199     va_end(args);
200     g_warning("\n");
201 #endif
204 void msg(const char *fmt, ...)
206 #if 0
207     va_list args;
208     fprintf(stdout, "JavaBinderyImpl:");
209     va_start(args, fmt);
210     vfprintf(stdout, fmt, args);
211     va_end(args);
212     fprintf(stdout, "\n");
213 #else
214     va_list args;
215     g_message("JavaBinderyImpl:");
216     va_start(args, fmt);
217     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
218     va_end(args);
219     g_message("\n");
220 #endif
225 //########################################################################
226 //# W I N 3 2      S T Y L E
227 //########################################################################
228 #ifdef __WIN32__
231 #define DIR_SEPARATOR "\\"
232 #define PATH_SEPARATOR ";"
236 static bool getRegistryString(HKEY root, const char *keyName,
237                const char *valName, char *buf, int buflen)
239     HKEY key;
240     DWORD bufsiz  = buflen;
241     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
242     int ret = RegQueryValueEx(key, TEXT(valName),
243             NULL, NULL, (BYTE *)buf, &bufsiz);
244     if (ret != ERROR_SUCCESS)
245         {
246         err("Key '%s\\%s not found\n", keyName, valName);
247         return false;
248         }
249     RegCloseKey(key);
250     return true;
254 static CreateVMFunc getCreateVMFunc()
256     char verbuf[16];
257     char regpath[80];
258     strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
259     bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
260                      regpath, "CurrentVersion", verbuf, 15);
261     if (!ret)
262         {
263         err("JVM CurrentVersion not found in registry\n");
264         return NULL;
265         }
266     strcat(regpath, "\\");
267     strcat(regpath, verbuf);
268     //msg("reg path: %s\n", regpath);
269     char libname[80];
270     ret = getRegistryString(HKEY_LOCAL_MACHINE,
271                      regpath, "RuntimeLib", libname, 79);
272     if (!ret)
273         {
274         err("Current JVM RuntimeLib not found in registry\n");
275         return NULL;
276         }
277     //msg("jvm path: %s\n", libname);
278     HMODULE lib = LoadLibrary(libname);
279     if (!lib)
280         {
281         err("Java VM not found at '%s'", libname);
282         return NULL;
283         }
284     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
285     if (!createVM)
286         {
287         err("Could not find 'JNI_CreateJavaVM' in shared library");
288         return NULL;
289         }
290     return createVM;
293 static void getJavaRoot(String &javaroot)
295     char exeName[80];
296     GetModuleFileName(NULL, exeName, 80);
297     char *slashPos = strrchr(exeName, '\\');
298     if (slashPos)
299         *slashPos = '\0';
300     javaroot = exeName;
301     javaroot.append("\\");
302     javaroot.append(INKSCAPE_JAVADIR);
308 //########################################################################
309 //# U N I X    S T Y L E
310 //########################################################################
311 #else /* !__WIN32__ */
314 #define DIR_SEPARATOR "/"
315 #define PATH_SEPARATOR ":"
318 /**
319  * Recursively descend into a directory looking for libjvm.so
320  */
321 static bool findJVMRecursive(const String &dirpath,
322                              std::vector<String> &results)
324     DIR *dir = opendir(dirpath.c_str());
325     if (!dir)
326         return false;
327     bool ret = false;
328     while (true)
329         {
330         struct dirent *de = readdir(dir);
331         if (!de)
332             break;
333         String fname = de->d_name;
334         if (fname == "." || fname == "..")
335             continue;
336         String path = dirpath;
337         path.push_back('/');
338         path.append(fname);
339         if (fname == "libjvm.so")
340             {
341             ret = true;
342             results.push_back(path);
343             continue;
344             }
345         struct stat finfo;
346         if (lstat(path.c_str(), &finfo)<0)
347             {
348             break;
349             }
350         if (finfo.st_mode & S_IFDIR)
351             {
352             ret |= findJVMRecursive(path, results);
353             }
354         }
355     closedir(dir);
356     return ret;
360 static const char *commonJavaPaths[] =
362     "/usr/java",
363     "/usr/local/java",
364     "/usr/lib/jvm",
365     "/usr/local/lib/jvm",
366     NULL
367 };
369 /**
370  * Look for a Java VM (libjvm.so) in several Unix places
371  */
372 static bool findJVM(String &result)
374     std::vector<String> results;
375     int found = false;
377     /* Is there one specified by the user? */
378     const char *javaHome = getenv("JAVA_HOME");
379     if (javaHome && findJVMRecursive(javaHome, results))
380         found = true;
381     else for (const char **path = commonJavaPaths ; *path ; path++)
382         {
383         if (findJVMRecursive(*path, results))
384             {
385             found = true;
386             break;
387             }
388         }
389     if (!found)
390         {
391         return false;
392         }
393     if (results.size() == 0)
394         return false;
395     //Look first for a Client VM
396     for (unsigned int i=0 ; i<results.size() ; i++)
397         {
398         String s = results[i];
399         if (s.find("client") != s.npos)
400             {
401             result = s;
402             return true;
403             }
404         }
405     //else default to the first
406     result = results[0];
407     return true;
412 static CreateVMFunc getCreateVMFunc()
414     String libname;
415     if (!findJVM(libname))
416         {
417         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
418         return NULL;
419         }
420     void *lib = dlopen(libname.c_str(), RTLD_NOW);
421     if (!lib)
422         {
423         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
424         return NULL;
425         }
426     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
427     if (!createVM)
428         {
429         err("Could not find 'JNI_CreateJavaVM' in shared library");
430             return NULL;
431         }
432     return createVM;
436 static void getJavaRoot(String &javaroot)
438     javaroot = INKSCAPE_JAVADIR;
441 #endif /* !__WIN32__ */
444 //########################################################################
445 //# COMMON
446 //########################################################################
449 bool JavaBinderyImpl::isLoaded()
451     return (jvm != (void *)0);
456 /**
457  * This will set up the classpath for the launched VM.
458  * We will add two things:
459  *   1.  INKSCAPE_JAVADIR/classes -- path to loose classes
460  *   2.  A concatenation of all jar files in INKSCAPE_JAVADIR/lib
461  *
462  * This will allow people to add classes and jars to the JVM without
463  * needing to state them explicitly.
464  * 
465  * @param javaroot.  Should be INKSCAPE_JAVADIR
466  * @param result a string buffer to hold the result of this method   
467  */        
468 static void populateClassPath(const String &javaroot,
469                               String &result)
471     String classdir = javaroot;
472     classdir.append(DIR_SEPARATOR);
473     classdir.append("classes");
475     String cp = classdir;
477     String libdir = javaroot;
478     libdir.append(DIR_SEPARATOR);
479     libdir.append("lib");
481     DIR *dir = opendir(libdir.c_str());
482     if (!dir)
483         {
484         result = cp;
485         return;
486         }
488     while (true)
489         {
490         struct dirent *de = readdir(dir);
491         if (!de)
492             break;
493         String fname = de->d_name;
494         if (fname == "." || fname == "..")
495             continue;
496         if (fname.size()<5) //x.jar
497             continue;
498         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
499             continue;
501         String path = libdir;
502         path.append(DIR_SEPARATOR);
503         path.append(fname);
505         cp.append(PATH_SEPARATOR);
506         cp.append(path);
507         }
508     closedir(dir);
510     result = cp;
512     return;
517 //========================================================================
518 // Native methods
519 //========================================================================
520 /**
521  * These methods are used to allow the ScriptRunner class to
522  * redirect its stderr and stdout streams to here, to be caught
523  * by two string buffers.  We can then use those buffers how we
524  * want.  These native methods are only those needed for running
525  * a script.  For the main C++/Java bindings, see dobinding.cpp 
526  */    
527 static void stdOutWrite(jlong ptr, jint ch)
529     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
530     bind->stdOut(ch);
533 static void stdErrWrite(jlong ptr, jint ch)
535     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
536     bind->stdErr(ch);
540 static JNINativeMethod scriptRunnerMethods[] =
542 { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite },
543 { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite },
544 { NULL,  NULL, NULL }
545 };
546 //========================================================================
547 // End native methods
548 //========================================================================
553 /**
554  * This is the most important part of this class.  Here we
555  * attempt to find, load, and initialize a java (or mlvm?) virtual
556  * machine.
557  * 
558  * @return true if successful, else false    
559  */ 
560 bool JavaBinderyImpl::loadJVM()
562     if (jvm)
563         return true;
565     CreateVMFunc createVM = getCreateVMFunc();
566     if (!createVM)
567         {
568         err("Could not find 'JNI_CreateJavaVM' in shared library");
569         return false;
570         }
572     String javaroot;
573     getJavaRoot(javaroot);
574     String cp;
575     populateClassPath(javaroot, cp);
576     String classpath = "-Djava.class.path=";
577     classpath.append(cp);
578     msg("Class path is: '%s'", classpath.c_str());
580     String libpath = "-Djava.library.path=";
581     libpath.append(javaroot);
582     libpath.append(DIR_SEPARATOR);
583     libpath.append("libm");
584     msg("Lib path is: '%s'", libpath.c_str());
586     JavaVMInitArgs vm_args;
587     JavaVMOption options[2];
588     options[0].optionString    = (char *)classpath.c_str();
589     options[1].optionString    = (char *)libpath.c_str();
590     vm_args.version            = JNI_VERSION_1_2;
591     vm_args.options            = options;
592     vm_args.nOptions           = 2;
593     vm_args.ignoreUnrecognized = true;
595     if (createVM(&jvm, &env, &vm_args) < 0)
596         {
597         err("JNI_GetDefaultJavaVMInitArgs() failed");
598         return false;
599         }
601     if (!registerNatives("org/inkscape/cmn/ScriptRunner",
602              scriptRunnerMethods))
603         {
604         return false;
605         }
606     return true;
611 /**
612  *  This is a difficult method.  What we are doing is trying to
613  *  call a static method with a list of arguments.  Similar to 
614  *  a varargs call, we need to marshal the Values into their
615  *  Java equivalents and make the proper call.
616  *  
617  * @param type the return type of the method
618  * @param className the full (package / name) name of the java class
619  * @param methodName the name of the method being invoked
620  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
621  *    that describes the param and return types of the method.
622  * @param retval the return value of the java method
623  * @return true if the call was successful, else false.  This is not
624  *    the return value of the method.    
625  */    
626 bool JavaBinderyImpl::callStatic(int type,
627                         const String &className,
628                         const String &methodName,
629                         const String &signature,
630                         const std::vector<Value> &params,
631                         Value &retval)
633     jclass cls = env->FindClass(className.c_str());
634     if (!cls)
635         {
636         err("Could not find class '%s'", className.c_str());
637         return false;
638         }
639     jmethodID mid = env->GetStaticMethodID(cls,
640                 methodName.c_str(), signature.c_str());
641     if (!mid)
642         {
643         err("Could not find method '%s:%s/%s'", className.c_str(),
644                 methodName.c_str(), signature.c_str());
645         return false;
646         }
647     /**
648      * Assemble your parameters into a form usable by JNI
649      */
650     jvalue *jvals = new jvalue[params.size()];
651     for (unsigned int i=0 ; i<params.size() ; i++)
652         {
653         Value v = params[i];
654         switch (v.getType())
655             {
656             case Value::BIND_BOOLEAN:
657                 {
658                 jvals[i].z = (jboolean)v.getBoolean();
659                 break;
660                 }
661             case Value::BIND_INT:
662                 {
663                 jvals[i].i = (jint)v.getInt();
664                 break;
665                 }
666             case Value::BIND_DOUBLE:
667                 {
668                 jvals[i].d = (jdouble)v.getDouble();
669                 break;
670                 }
671             case Value::BIND_STRING:
672                 {
673                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
674                 break;
675                 }
676             default:
677                 {
678                 err("Unknown value type: %d", v.getType());
679                 return false;
680                 }
681             }
682         }
683     switch (type)
684         {
685         case Value::BIND_VOID:
686             {
687             env->CallStaticVoidMethodA(cls, mid, jvals);
688             break;
689             }
690         case Value::BIND_BOOLEAN:
691             {
692             env->CallStaticBooleanMethodA(cls, mid, jvals);
693             break;
694             }
695         case Value::BIND_INT:
696             {
697             env->CallStaticIntMethodA(cls, mid, jvals);
698             break;
699             }
700         case Value::BIND_DOUBLE:
701             {
702             env->CallStaticDoubleMethodA(cls, mid, jvals);
703             break;
704             }
705         case Value::BIND_STRING:
706             {
707             env->CallStaticObjectMethodA(cls, mid, jvals);
708             break;
709             }
710         default:
711             {
712             err("Unknown return type: %d", type);
713             return false;
714             }
715         }
716     delete jvals;
717     return true;
722 /**
723  * Convenience method to call the static void main(String argv[])
724  * method of a given class
725  * 
726  * @param className full name of the java class
727  * @return true if successful, else false   
728  */ 
729 bool JavaBinderyImpl::callMain(const String &className)
731     std::vector<Value> parms;
732     Value retval;
733     return callStatic(Value::BIND_VOID, className, "main",
734              "([Ljava/lang/String;)V", parms, retval);
738 /**
739  * Used to register an array of native methods for a named class
740  * 
741  * @param className the full name of the java class
742  * @param the method array
743  * @return true if successful, else false     
744  */ 
745 bool JavaBinderyImpl::registerNatives(const String &className,
746                            const JNINativeMethod *methods)
748     jclass cls = env->FindClass(className.c_str());
749     if (!cls)
750         {
751         err("Could not find class '%s'", className.c_str());
752         return false;
753         }
754     int nrMethods = 0;
755     for (const JNINativeMethod *m = methods ; m->name ; m++)
756         nrMethods++;
757     if (env->RegisterNatives(cls, (const JNINativeMethod *)methods, nrMethods) < 0)
758         {
759         err("Could not register natives");
760         return false;
761         }
762     return true;
768 } // namespace Bind
769 } // namespace Inkscape
771 //########################################################################
772 //# E N D    O F    F I L E
773 //########################################################################