Code

Initial capability to load current document into script
[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>
56 //For repr and document
57 #include <document.h>
58 #include <inkscape.h>
59 #include <xml/repr.h>
61 /**
62  * Note: We must limit Java or JVM-specific code to this file
63  * and to dobinding.cpp.  It should be hidden from javabind.h
64  * 
65  * This file is mostly about getting things up and running, and
66  * providing the basic C-to-Java hooks.
67  *   
68  * dobinding.cpp will have the rote and repetitious
69  * class-by-class binding   
70  */  
73 namespace Inkscape
74 {
76 namespace Bind
77 {
80 //########################################################################
81 //# DEFINITIONS
82 //########################################################################
84 typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *);
88 //########################################################################
89 //# UTILITY
90 //########################################################################
92 /**
93  * Normalize path.  Java wants '/', even on Windows
94  */ 
95 String normalizePath(const String &str)
96 {
97     String buf;
98     for (unsigned int i=0 ; i<str.size() ; i++)
99         {
100         char ch = str[i];
101         if (ch == '\\')
102             buf.push_back('/');
103         else
104             buf.push_back(ch);
105                 }
106         return buf;
109 String getExceptionString(JNIEnv *env)
111     String buf;
112     jthrowable exc = env->ExceptionOccurred();
113     if (!exc)
114         return buf;
115     jclass cls = env->GetObjectClass(exc);
116     jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
117     jstring jstr = (jstring) env->CallObjectMethod(exc, mid);
118     const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
119     buf.append(str);
120     env->ReleaseStringUTFChars(jstr, str);
121     env->ExceptionClear();
122         return buf;
125 jint getObjInt(JNIEnv *env, jobject obj, const char *name)
127     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
128     return env->GetIntField(obj, fid);
131 void setObjInt(JNIEnv *env, jobject obj, const char *name, jint val)
133     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I");
134     env->SetIntField(obj, fid, val);
137 jlong getObjLong(JNIEnv *env, jobject obj, const char *name)
139     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
140     return env->GetLongField(obj, fid);
143 void setObjLong(JNIEnv *env, jobject obj, const char *name, jlong val)
145     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J");
146     env->SetLongField(obj, fid, val);
149 jfloat getObjFloat(JNIEnv *env, jobject obj, const char *name)
151     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
152     return env->GetFloatField(obj, fid);
155 void setObjFloat(JNIEnv *env, jobject obj, const char *name, jfloat val)
157     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F");
158     env->SetFloatField(obj, fid, val);
161 jdouble getObjDouble(JNIEnv *env, jobject obj, const char *name)
163     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
164     return env->GetDoubleField(obj, fid);
167 void setObjDouble(JNIEnv *env, jobject obj, const char *name, jdouble val)
169     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D");
170     env->SetDoubleField(obj, fid, val);
173 String getString(JNIEnv *env, jstring jstr)
175     const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE);
176     String str = chars;
177     env->ReleaseStringUTFChars(jstr, chars);
178     return str;
181 String getObjString(JNIEnv *env, jobject obj, const char *name)
183     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
184     jstring jstr = (jstring)env->GetObjectField(obj, fid);
185     return getString(env, jstr);
188 void setObjString(JNIEnv *env, jobject obj, const char *name, const String &val)
190     jstring jstr = env->NewStringUTF(val.c_str());
191     jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;");
192     env->SetObjectField(obj, fid, jstr);
198 //########################################################################
199 //# CONSTRUCTOR/DESTRUCTOR
200 //########################################################################
202 static JavaBinderyImpl *_instance = NULL;
204 JavaBindery *JavaBindery::getInstance()
206     return JavaBinderyImpl::getInstance();
209 JavaBinderyImpl *JavaBinderyImpl::getInstance()
211     if (!_instance)
212         {
213         _instance = new JavaBinderyImpl();
214         }
215     return _instance;
218 JavaBinderyImpl::JavaBinderyImpl()
220     jvm        = NULL;
221     env        = NULL;
222     gatewayObj = NULL;
225 JavaBinderyImpl::~JavaBinderyImpl()
230 //########################################################################
231 //# MESSAGES
232 //########################################################################
234 void err(const char *fmt, ...)
236 #if 0
237     va_list args;
238     fprintf(stderr, "JavaBinderyImpl err:");
239     va_start(args, fmt);
240     vfprintf(stderr, fmt, args);
241     va_end(args);
242     fprintf(stderr, "\n");
243 #else
244     va_list args;
245     g_warning("JavaBinderyImpl err:");
246     va_start(args, fmt);
247     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args);
248     va_end(args);
249     g_warning("\n");
250 #endif
253 void msg(const char *fmt, ...)
255 #if 0
256     va_list args;
257     fprintf(stdout, "JavaBinderyImpl:");
258     va_start(args, fmt);
259     vfprintf(stdout, fmt, args);
260     va_end(args);
261     fprintf(stdout, "\n");
262 #else
263     va_list args;
264     g_message("JavaBinderyImpl:");
265     va_start(args, fmt);
266     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
267     va_end(args);
268     g_message("\n");
269 #endif
274 //########################################################################
275 //# W I N 3 2      S T Y L E
276 //########################################################################
277 #ifdef __WIN32__
280 #define DIR_SEPARATOR "\\"
281 #define PATH_SEPARATOR ";"
285 static bool getRegistryString(HKEY /*root*/, const char *keyName,
286                const char *valName, char *buf, int buflen)
288     HKEY key;
289     DWORD bufsiz  = buflen;
290     RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
291     int ret = RegQueryValueEx(key, TEXT(valName),
292             NULL, NULL, (BYTE *)buf, &bufsiz);
293     if (ret != ERROR_SUCCESS)
294         {
295         err("Key '%s\\%s not found\n", keyName, valName);
296         return false;
297         }
298     RegCloseKey(key);
299     return true;
303 static String cleanPath(const String &s)
305     String buf;
306     for (unsigned int i=0 ; i<s.size() ; i++)
307         {
308         char ch = s[i];
309         if (ch != '"')
310             buf.push_back(ch);
311                 }
312         return buf;
316 /**
317  * Common places to find jvm.dll under JAVA_HOME
318  */ 
319 static const char *commonJavaPaths[] =
321     "\\jre\\bin\\client\\jvm.dll",
322     "\\bin\\client\\jvm.dll",
323     "\\jvm.dll",
324     NULL
325 };
327 static CreateVMFunc getCreateVMFunc()
329     bool found = false;
330     String libname;
332     /**
333      * First, look for JAVA_HOME.  This will allow the user
334      * to override what's in the registry
335      */              
336     const char *envStr = getenv("JAVA_HOME");
337     if (envStr)
338         {
339         String javaHome = cleanPath(envStr);
340         msg("JAVA_HOME='%s'", javaHome.c_str());
341         for (const char **path = commonJavaPaths ; *path ; path++)
342             {
343             String jpath = javaHome;
344             jpath.append(*path);
345             //msg("trying '%s'", jpath.c_str());
346             struct stat finfo;
347             if (stat(jpath.c_str(), &finfo)>=0)
348                 {
349                 //msg("found");
350                 libname = jpath;
351                 found = true;
352                 break;
353                 }
354             }
355         }
357     //not at JAVA_HOME.  check the registry
358     if (!found)
359         {
360         char verbuf[16];
361         char regpath[80];
362         strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
363         bool ret = getRegistryString(HKEY_LOCAL_MACHINE,
364                      regpath, "CurrentVersion", verbuf, 15);
365         if (!ret)
366             {
367             msg("JVM CurrentVersion not found in registry at '%s'", regpath);
368             }
369         else
370             {
371             strcat(regpath, "\\");
372             strcat(regpath, verbuf);
373             //msg("reg path: %s\n", regpath);
374             char valbuf[80];
375             ret = getRegistryString(HKEY_LOCAL_MACHINE,
376                      regpath, "RuntimeLib", valbuf, 79);
377             if (ret)
378                 {
379                 found = true;
380                 libname = valbuf;
381                 }
382             else
383                 {
384                 msg("JVM RuntimeLib not found in registry at '%s'",
385                                           regpath);
386                                 }
387                         }
388         }
390     if (!found)
391         {
392         err("JVM not found at JAVA_HOME or in registry");
393         return NULL;
394         }
396     /**
397      * If we are here, then we seem to have a valid path for jvm.dll
398      * Give it a try
399      */              
400     msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
401     HMODULE lib = LoadLibrary(libname.c_str());
402     if (!lib)
403         {
404         err("Java VM not found at '%s'", libname.c_str());
405         return NULL;
406         }
407     CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM");
408     if (!createVM)
409         {
410         err("Could not find 'JNI_CreateJavaVM' in shared library '%s'",
411                                    libname.c_str());
412         return NULL;
413         }
414     return createVM;
417 static void getJavaRoot(String &javaroot)
419     char exeName[80];
420     GetModuleFileName(NULL, exeName, 80);
421     char *slashPos = strrchr(exeName, '\\');
422     if (slashPos)
423         *slashPos = '\0';
424     javaroot = exeName;
425     javaroot.append("\\");
426     javaroot.append(INKSCAPE_BINDDIR);
427     javaroot.append("\\java");
433 //########################################################################
434 //# U N I X    S T Y L E
435 //########################################################################
436 #else /* !__WIN32__ */
439 #define DIR_SEPARATOR "/"
440 #define PATH_SEPARATOR ":"
443 /**
444  * Recursively descend into a directory looking for libjvm.so
445  */
446 static bool findJVMRecursive(const String &dirpath,
447                              std::vector<String> &results)
449     DIR *dir = opendir(dirpath.c_str());
450     if (!dir)
451         return false;
452     bool ret = false;
453     while (true)
454         {
455         struct dirent *de = readdir(dir);
456         if (!de)
457             break;
458         String fname = de->d_name;
459         if (fname == "." || fname == "..")
460             continue;
461         String path = dirpath;
462         path.push_back('/');
463         path.append(fname);
464         if (fname == "libjvm.so")
465             {
466             ret = true;
467             results.push_back(path);
468             continue;
469             }
470         struct stat finfo;
471         if (lstat(path.c_str(), &finfo)<0)
472             {
473             break;
474             }
475         if (finfo.st_mode & S_IFDIR)
476             {
477             ret |= findJVMRecursive(path, results);
478             }
479         }
480     closedir(dir);
481     return ret;
485 static const char *commonJavaPaths[] =
487     "/usr/lib/jvm/jre",
488     "/usr/lib/jvm",
489     "/usr/local/lib/jvm/jre",
490     "/usr/local/lib/jvm",
491     "/usr/java",
492     "/usr/local/java",
493     NULL
494 };
496 /**
497  * Look for a Java VM (libjvm.so) in several Unix places
498  */
499 static bool findJVM(String &result)
501     std::vector<String> results;
502     int found = false;
504     /* Is there one specified by the user? */
505     const char *javaHome = getenv("JAVA_HOME");
506     if (javaHome && findJVMRecursive(javaHome, results))
507         found = true;
508     else for (const char **path = commonJavaPaths ; *path ; path++)
509         {
510         if (findJVMRecursive(*path, results))
511             {
512             found = true;
513             break;
514             }
515         }
516     if (!found)
517         {
518         return false;
519         }
520     if (results.size() == 0)
521         return false;
522     //Look first for a Client VM
523     for (unsigned int i=0 ; i<results.size() ; i++)
524         {
525         String s = results[i];
526         if (s.find("client") != s.npos)
527             {
528             result = s;
529             return true;
530             }
531         }
532     //else default to the first
533     result = results[0];
534     return true;
539 static CreateVMFunc getCreateVMFunc()
541     String libname;
542     if (!findJVM(libname))
543         {
544         err("No Java VM found. Is JAVA_HOME defined?  Need to find 'libjvm.so'");
545         return NULL;
546         }
547     msg("getCreateVMFunc: Loading JVM: %s", libname.c_str());
548     void *lib = dlopen(libname.c_str(), RTLD_NOW);
549     if (!lib)
550         {
551         err("Java VM not found at '%s' : %s", libname.c_str(), strerror(errno));
552         return NULL;
553         }
554     CreateVMFunc createVM = (CreateVMFunc)dlsym(lib, "JNI_CreateJavaVM");
555     if (!createVM)
556         {
557         err("Could not find 'JNI_CreateJavaVM' in shared library");
558             return NULL;
559         }
560     return createVM;
564 static void getJavaRoot(String &javaroot)
566     javaroot = INKSCAPE_BINDDIR;
567     javaroot.append("/java");
570 #endif /* !__WIN32__ */
573 //########################################################################
574 //# COMMON
575 //########################################################################
578 bool JavaBinderyImpl::isLoaded()
580     return (jvm != (void *)0);
585 /**
586  * This will set up the classpath for the launched VM.
587  * We will add two things:
588  *   1.  INKSCAPE_JAVADIR/classes -- path to loose classes
589  *   2.  A concatenation of all jar files in INKSCAPE_JAVADIR/lib
590  *
591  * This will allow people to add classes and jars to the JVM without
592  * needing to state them explicitly.
593  * 
594  * @param javaroot.  Should be INKSCAPE_JAVADIR
595  * @param result a string buffer to hold the result of this method   
596  */        
597 static void populateClassPath(const String &javaroot,
598                               String &result)
600     String classdir = javaroot;
601     classdir.append(DIR_SEPARATOR);
602     classdir.append("classes");
604     String cp = classdir;
606     String libdir = javaroot;
607     libdir.append(DIR_SEPARATOR);
608     libdir.append("lib");
610     DIR *dir = opendir(libdir.c_str());
611     if (!dir)
612         {
613         result = cp;
614         return;
615         }
617     while (true)
618         {
619         struct dirent *de = readdir(dir);
620         if (!de)
621             break;
622         String fname = de->d_name;
623         if (fname == "." || fname == "..")
624             continue;
625         if (fname.size()<5) //x.jar
626             continue;
627         if (fname.compare(fname.size()-4, 4, ".jar") != 0)
628             continue;
630         String path = libdir;
631         path.append(DIR_SEPARATOR);
632         path.append(fname);
634         cp.append(PATH_SEPARATOR);
635         cp.append(path);
636         }
637     closedir(dir);
638     
639     result = cp;
644 //========================================================================
645 // Gateway
646 //========================================================================
647 /**
648  * This is provided to scripts can grab the current copy or the
649  * repr tree.  If anyone has a smarter way of doing this, please implement. 
650  */    
651 jstring JNICALL documentGet(JNIEnv *env, jobject /*obj*/, jlong ptr)
653     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
654     String buf =  sp_repr_save_buf((SP_ACTIVE_DOCUMENT)->rdoc);
655     jstring jstr = env->NewStringUTF(buf.c_str());
656     return jstr;
659 /**
660  * This is provided to scripts can load an XML tree into Inkscape.
661  * If anyone has a smarter way of doing this, please implement. 
662  */    
663 jboolean JNICALL documentSet(JNIEnv *env, jobject /*obj*/, jlong ptr, jstring jstr)
665     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
666     String s = getString(env, jstr);
667     SPDocument *doc = sp_document_new_from_mem(s.c_str(), s.size(), true);
670 /**
671  * This method is used to allow the gateway class to
672  * redirect its logging stream here.
673  * For the main C++/Java bindings, see dobinding.cpp 
674  */    
675 void JNICALL logWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch)
677     JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr;
678     bind->log(ch);
682 static JNINativeMethod gatewayMethods[] =
684 { (char *)"documentGet", (char *)"(J)Ljava/lang/String;",  (void *)documentGet },
685 { (char *)"documentSet", (char *)"(JLjava/lang/String;)Z", (void *)documentSet },
686 { (char *)"logWrite",    (char *)"(JI)V",                  (void *)logWrite    },
687 { NULL,  NULL, NULL }
688 };
691 /**
692  * This sets up the 'Gateway' java class for execution of
693  * scripts.   The class's constructor takes a jlong.  This java long
694  * is used to store the pointer to 'this'.  When ScriptRunner makes
695  * native calls, it passes that jlong back, so that it can call the
696  * methods of this C++ class.  
697  */  
698 bool JavaBinderyImpl::setupGateway()
700     String className = "org/inkscape/cmn/Gateway";
701     if (!registerNatives(className, gatewayMethods))
702         {
703         return false;
704         }
705     jclass cls = env->FindClass(className.c_str());
706     if (!cls)
707         {
708         err("setupGateway: cannot find class '%s' : %s",
709                          className.c_str(), getException().c_str());
710         return false;
711                 }
712         jmethodID mid = env->GetMethodID(cls, "<init>", "(J)V");
713         if (!mid)
714         {
715         err("setupGateway: cannot find constructor for '%s' : %s",
716                           className.c_str(), getException().c_str());
717         return false;
718                 }
719     gatewayObj = env->NewObject(cls, mid, ((jlong)this));
720     if (!gatewayObj)
721         {
722         err("setupGateway: cannot construct '%s' : %s",
723                          className.c_str(), getException().c_str());
724         return false;
725                 }
727         msg("Gateway ready");
728     return true;
731 bool JavaBinderyImpl::scriptRun(const String &lang, const String &script)
733     if (!loadJVM())
734         return false;
736     std::vector<Value> params;
737     Value langParm(lang);
738     params.push_back(langParm);
739     Value scriptParm(script);
740     params.push_back(scriptParm);
741     Value retval;
742     callInstance(Value::BIND_VOID, gatewayObj, "scriptRun",
743              "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
744     return retval.getBoolean();
747 bool JavaBinderyImpl::scriptRunFile(const String &lang, const String &fname)
749     if (!loadJVM())
750         return false;
752     std::vector<Value> params;
753     Value langParm(lang);
754     params.push_back(langParm);
755     Value fnameParm(fname);
756     params.push_back(fnameParm);
757     Value retval;
758     callInstance(Value::BIND_VOID, gatewayObj, "scriptRunFile",
759              "(Ljava/lang/String;Ljava/lang/String;)Z", params, retval);
760     return retval.getBoolean();
763 bool JavaBinderyImpl::showConsole()
765     if (!loadJVM())
766         return false;
767         
768     std::vector<Value> params;
769     Value retval;
770     callInstance(Value::BIND_VOID, gatewayObj, "showConsole",
771              "()Z", params, retval);
772     return retval.getBoolean();
776 //========================================================================
777 // End Gateway
778 //========================================================================
781 /**
782  * This is used to grab output from the VM itself. See 'options' below.
783  */ 
784 static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args)
786     g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
790 /**
791  * This is the most important part of this class.  Here we
792  * attempt to find, load, and initialize a java (or mlvm?) virtual
793  * machine.
794  * 
795  * @return true if successful, else false    
796  */ 
797 bool JavaBinderyImpl::loadJVM()
799     if (jvm)
800         return true;
802     CreateVMFunc createVM = getCreateVMFunc();
803     if (!createVM)
804         {
805         err("Could not find 'JNI_CreateJavaVM' in shared library");
806         return false;
807         }
809     String javaroot;
810     getJavaRoot(javaroot);
811     String cp;
812     populateClassPath(javaroot, cp);
813     String classpath = "-Djava.class.path=";
814     classpath.append(normalizePath(cp));
815     msg("Class path is: '%s'", classpath.c_str());
817     String libpath = "-Djava.library.path=";
818     libpath.append(javaroot);
819     libpath.append(DIR_SEPARATOR);
820     libpath.append("libm");
821     libpath = normalizePath(libpath);
822     msg("Lib path is: '%s'", libpath.c_str());
824     JavaVMInitArgs vm_args;
825     JavaVMOption options[10];//should be enough
826     int nOptions = 0;
827     options[nOptions++].optionString = (char *)classpath.c_str();
828     options[nOptions++].optionString = (char *)libpath.c_str();
829     //options[nOptions++].optionString = (char *)"-verbose:jni";
830     options[nOptions  ].optionString = (char *)"vfprintf";
831     options[nOptions++].extraInfo    = (void *)vfprintfHook;
832     vm_args.version                  = JNI_VERSION_1_4;
833     vm_args.options                  = options;
834     vm_args.nOptions                 = nOptions;
835     vm_args.ignoreUnrecognized       = true;
837     if (createVM(&jvm, &env, &vm_args) < 0)
838         {
839         err("JNI_CreateJavaVM() failed");
840         return false;
841         }
843     //get jvm version
844     jint vers = env->GetVersion();
845     int versionMajor = (vers>>16) & 0xffff;
846     int versionMinor = (vers    ) & 0xffff;
847     msg("Loaded JVM version %d.%d", versionMajor, versionMinor);
849     if (!setupGateway())
850         return false;
852     return true;
856 /**
857  *  This is a difficult method.  What we are doing is trying to
858  *  call a static method with a list of arguments.  Similar to 
859  *  a varargs call, we need to marshal the Values into their
860  *  Java equivalents and make the proper call.
861  *  
862  * @param type the return type of the method
863  * @param className the full (package / name) name of the java class
864  * @param methodName the name of the method being invoked
865  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
866  *    that describes the param and return types of the method.
867  * @param retval the return value of the java method
868  * @return true if the call was successful, else false.  This is not
869  *    the return value of the method.    
870  */    
871 bool JavaBinderyImpl::callStatic(int type,
872                         const String &className,
873                         const String &methodName,
874                         const String &signature,
875                         const std::vector<Value> &params,
876                         Value &retval)
878     jclass cls = env->FindClass(className.c_str());
879     if (!cls)
880         {
881         err("Could not find class '%s' : %s",
882                        className.c_str(), getException().c_str());
883         return false;
884         }
885     jmethodID mid = env->GetStaticMethodID(cls,
886                 methodName.c_str(), signature.c_str());
887     if (!mid)
888         {
889         err("Could not find method '%s:%s/%s' : %s",
890                         className.c_str(), methodName.c_str(),
891                             signature.c_str(), getException().c_str());
892         return false;
893         }
894     /**
895      * Assemble your parameters into a form usable by JNI
896      */
897     jvalue *jvals = new jvalue[params.size()];
898     for (unsigned int i=0 ; i<params.size() ; i++)
899         {
900         Value v = params[i];
901         switch (v.getType())
902             {
903             case Value::BIND_BOOLEAN:
904                 {
905                 jvals[i].z = (jboolean)v.getBoolean();
906                 break;
907                 }
908             case Value::BIND_INT:
909                 {
910                 jvals[i].i = (jint)v.getInt();
911                 break;
912                 }
913             case Value::BIND_DOUBLE:
914                 {
915                 jvals[i].d = (jdouble)v.getDouble();
916                 break;
917                 }
918             case Value::BIND_STRING:
919                 {
920                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
921                 break;
922                 }
923             default:
924                 {
925                 err("Unknown value type: %d", v.getType());
926                 return false;
927                 }
928             }
929         }
930     switch (type)
931         {
932         case Value::BIND_VOID:
933             {
934             env->CallStaticVoidMethodA(cls, mid, jvals);
935             break;
936             }
937         case Value::BIND_BOOLEAN:
938             {
939             jboolean ret = env->CallStaticBooleanMethodA(cls, mid, jvals);
940             if (ret == JNI_TRUE) //remember, don't truncate
941                 retval.setBoolean(true);
942             else
943                 retval.setBoolean(false);
944             break;
945             }
946         case Value::BIND_INT:
947             {
948             jint ret = env->CallStaticIntMethodA(cls, mid, jvals);
949             retval.setInt(ret);
950             break;
951             }
952         case Value::BIND_DOUBLE:
953             {
954             jdouble ret = env->CallStaticDoubleMethodA(cls, mid, jvals);
955             retval.setDouble(ret);
956             break;
957             }
958         case Value::BIND_STRING:
959             {
960             jobject ret = env->CallStaticObjectMethodA(cls, mid, jvals);
961             jstring jstr = (jstring) ret;
962             const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
963             retval.setString(str);
964             env->ReleaseStringUTFChars(jstr, str);
965             break;
966             }
967         default:
968             {
969             err("Unknown return type: %d", type);
970             return false;
971             }
972         }
973     delete jvals;
974     String errStr = getException();
975     if (errStr.size()>0)
976         {
977         err("callStatic: %s", errStr.c_str());
978         return false;
979                 }
980     return true;
985 /**
986  *  Another difficult method.  However, this time we are operating
987  *  on an existing instance jobject. 
988  *  
989  * @param type the return type of the method
990  * @param obj the instance upon which to make the call
991  * @param methodName the name of the method being invoked
992  * @param signature the method signature (ex: "(Ljava/lang/String;I)V" )
993  *    that describes the param and return types of the method.
994  * @param retval the return value of the java method
995  * @return true if the call was successful, else false.  This is not
996  *    the return value of the method.    
997  */    
998 bool JavaBinderyImpl::callInstance(
999                         int type,
1000                         const jobject obj,
1001                         const String &methodName,
1002                         const String &signature,
1003                         const std::vector<Value> &params,
1004                         Value &retval)
1006     jmethodID mid = env->GetMethodID(env->GetObjectClass(obj),
1007                 methodName.c_str(), signature.c_str());
1008     if (!mid)
1009         {
1010         err("Could not find method '%s/%s' : %s",
1011                         methodName.c_str(),
1012                             signature.c_str(), getException().c_str());
1013         return false;
1014         }
1015     /**
1016      * Assemble your parameters into a form usable by JNI
1017      */
1018     jvalue *jvals = new jvalue[params.size()];
1019     for (unsigned int i=0 ; i<params.size() ; i++)
1020         {
1021         Value v = params[i];
1022         switch (v.getType())
1023             {
1024             case Value::BIND_BOOLEAN:
1025                 {
1026                 jvals[i].z = (jboolean)v.getBoolean();
1027                 break;
1028                 }
1029             case Value::BIND_INT:
1030                 {
1031                 jvals[i].i = (jint)v.getInt();
1032                 break;
1033                 }
1034             case Value::BIND_DOUBLE:
1035                 {
1036                 jvals[i].d = (jdouble)v.getDouble();
1037                 break;
1038                 }
1039             case Value::BIND_STRING:
1040                 {
1041                 jvals[i].l = (jobject) env->NewStringUTF(v.getString().c_str());
1042                 break;
1043                 }
1044             default:
1045                 {
1046                 err("Unknown value type: %d", v.getType());
1047                 return false;
1048                 }
1049             }
1050         }
1051     switch (type)
1052         {
1053         case Value::BIND_VOID:
1054             {
1055             env->CallVoidMethodA(obj, mid, jvals);
1056             break;
1057             }
1058         case Value::BIND_BOOLEAN:
1059             {
1060             jboolean ret = env->CallBooleanMethodA(obj, mid, jvals);
1061             if (ret == JNI_TRUE) //remember, don't truncate
1062                 retval.setBoolean(true);
1063             else
1064                 retval.setBoolean(false);
1065             break;
1066             }
1067         case Value::BIND_INT:
1068             {
1069             jint ret = env->CallIntMethodA(obj, mid, jvals);
1070             retval.setInt(ret);
1071             break;
1072             }
1073         case Value::BIND_DOUBLE:
1074             {
1075             jdouble ret = env->CallDoubleMethodA(obj, mid, jvals);
1076             retval.setDouble(ret);
1077             break;
1078             }
1079         case Value::BIND_STRING:
1080             {
1081             jobject ret = env->CallObjectMethodA(obj, mid, jvals);
1082             jstring jstr = (jstring) ret;
1083             const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
1084             retval.setString(str);
1085             env->ReleaseStringUTFChars(jstr, str);
1086             break;
1087             }
1088         default:
1089             {
1090             err("Unknown return type: %d", type);
1091             return false;
1092             }
1093         }
1094     delete jvals;
1095     String errStr = getException();
1096     if (errStr.size()>0)
1097         {
1098         err("callStatic: %s", errStr.c_str());
1099         return false;
1100                 }
1101     return true;
1107 /**
1108  * Fetch the last exception from the JVM, if any.  Clear it to
1109  * continue processing
1110  * 
1111  * @return the exception's descriptio,if any.  Else ""  
1112  */  
1113 String JavaBinderyImpl::getException()
1115     return getExceptionString(env);
1120 /**
1121  * Convenience method to call the static void main(String argv[])
1122  * method of a given class
1123  * 
1124  * @param className full name of the java class
1125  * @args the argument strings to the method 
1126  * @return true if successful, else false   
1127  */ 
1128 bool JavaBinderyImpl::callMain(const String &className,
1129                                const std::vector<String> &args)
1131     std::vector<Value> parms;
1132     for (unsigned int i=0 ; i<args.size() ; i++)
1133         {
1134         Value v;
1135         v.setString(args[i]);
1136         parms.push_back(v);
1137                 }
1138     Value retval;
1139     return callStatic(Value::BIND_VOID, className, "main",
1140              "([Ljava/lang/String;)V", parms, retval);
1144 /**
1145  * Used to register an array of native methods for a named class
1146  * 
1147  * @param className the full name of the java class
1148  * @param the method array
1149  * @return true if successful, else false     
1150  */ 
1151 bool JavaBinderyImpl::registerNatives(const String &className,
1152                            const JNINativeMethod *methods)
1154     jclass cls = env->FindClass(className.c_str());
1155     if (!cls)
1156         {
1157         err("Could not find class '%s'", className.c_str());
1158         return false;
1159         }
1160     //msg("registerNatives: class '%s' found", className.c_str());
1161     
1162     /**
1163      * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522
1164      */
1165         jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors",
1166                   "()[Ljava/lang/reflect/Constructor;");
1167         if (!mid)
1168             {
1169             err("Could not get reflect mid for 'getConstructors' : %s",
1170              getException().c_str());
1171                 return false;
1172                 }
1173         jobject res = env->CallObjectMethod(cls, mid);
1174         if (!res)
1175             {
1176             err("Could not get constructors : %s", getException().c_str());
1177                 return false;
1178                 }
1179         /**
1180          * end hack
1181          */             
1182     jint nrMethods = 0;
1183     for (const JNINativeMethod *m = methods ; m->name ; m++)
1184         nrMethods++;
1185     jint ret = env->RegisterNatives(cls, methods, nrMethods);
1186     if (ret < 0)
1187         {
1188         err("Could not register %d native methods for '%s' : %s",
1189                     nrMethods, className.c_str(), getException().c_str());
1190         return false;
1191         }
1192     return true;
1198 } // namespace Bind
1199 } // namespace Inkscape
1201 //########################################################################
1202 //# E N D    O F    F I L E
1203 //########################################################################