/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *  
 * The Original Code is The Waterfall Java Plugin Module
 *  
 * The Initial Developer of the Original Code is Sun Microsystems Inc
 * Portions created by Sun Microsystems Inc are Copyright (C) 2001
 * All Rights Reserved.
 * 
 * $Id: java_plugin.c,v 1.3 2001-07-17 22:15:21 edburns%acm.org Exp $
 * 
 * Contributor(s):
 * 
 *     Nikolay N. Igotti <nikolay.igotti@Sun.Com>
 */

/**
 * Main Waterfall C file. Contains implementation of Waterfall API 
 *  for in-process case 
 **/

/* all methods here but JVMP_GetPlugin() declared "static", as only access to 
   them is via structure returned by JVMP_GetPlugin(), so why to add needless
   records to export table of plugin DLL. Also it looks like JNI approach. */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef XP_UNIX
#include <unistd.h>
#endif
#include <string.h>
#include "jvmp.h"
#include "jpthreads.h"

struct JVM_Methods {
  jint (JNICALL *JNI_GetDefaultJavaVMInitArgs)(void *args);
  jint (JNICALL *JNI_CreateJavaVM)(JavaVM **pvm, void **penv, void *args);
  jint (JNICALL *JNI_GetCreatedJavaVMs)(JavaVM **, jsize, jsize *);
}; 
typedef struct JVM_Methods JVM_Methods;

static JVMP_RuntimeContext    JVMP_PluginContext;
static JVMP_PluginDescription JVMP_Description;
static JavaVM*                JVMP_JVM_running = NULL;
static jobject                JVMP_PluginInstance = NULL;
static jclass                 JVMP_PluginClass = NULL;
static JVM_Methods            JVMP_JVMMethods;
static char*                  JVMP_plugin_home = NULL;
static char*                  JDK_home = NULL;
static char*                  global_libpath = NULL;
static MUTEX_T*               jvm_mutex = NULL;
  

#define PLUGIN_LOG(s1)  { fprintf (stderr, "WF: "); \
                          fprintf(stderr, s1); fprintf(stderr, "\n"); }
#define PLUGIN_LOG2(s1, s2) { fprintf (stderr, "WF: "); \
                        fprintf(stderr, s1, s2); fprintf(stderr, "\n"); }
#define PLUGIN_LOG3(s1, s2, s3) { fprintf (stderr, "WF: "); \
                        fprintf(stderr, s1, s2, s3); fprintf(stderr, "\n"); }
#define PLUGIN_LOG4(s1, s2, s3, s4) { fprintf (stderr, "WF: "); \
                        fprintf(stderr, s1, s2, s3 ,s4); fprintf(stderr, "\n"); }
#ifdef XP_UNIX
#define LIBJVM "libjvm.so"
#endif
#ifdef XP_WIN32
#define LIBJVM "jvm.dll"
#endif

/* default JNI version used by Waterfall */ 
#ifdef  WF_PJAVA
#define DEFAULT_JNI_VERSION JNI_VERSION_1_1
#else
#define DEFAULT_JNI_VERSION JNI_VERSION_1_2
#endif
/* level of verbosity printed with PlugableJVM.trace method:
   lesser number - lesser output */
#define DEBUG_LEVEL         10

/* XXX: localization */
static char* g_jvmp_errors[] =
{
  "no error",
  "no such class",
  "no such method",
  "file not found",
  "remote transport failed",
  "failed due unknown reasons",
  "out of memory",
  "null pointer passed",
  "initialization failed",
  "forbidden to perform this action",
  "java exception happened while handling this call",
  "java method failed",
  "invalid arguments",
  "those capabilities are not granted",
  NULL
};

static jint JVMP_addOption(JavaVMInitArgs* opt, char* str, void* extra) {
  jint numOp;

  numOp = opt->nOptions + 1;
  opt->options = realloc(opt->options, numOp * sizeof(JavaVMOption));
  if (!opt->options) 
    {
      PLUGIN_LOG("cannot realloc() memory for JVM options");
      return JNI_FALSE;
    };
  opt->options[numOp-1].optionString = str;
  opt->options[numOp-1].extraInfo = extra;
  opt->nOptions = numOp;
  return JNI_TRUE;
}

static void UpdateCaps(JVMP_CallingContext* ctx)
{
    JNIEnv* env = ctx->env;
    if (!env) return;
    if (ctx->jcaps == NULL)
	{
	    ctx->jcaps = (*env)->NewByteArray(env, sizeof(JVMP_SecurityCap));
	    if (ctx->jcaps == NULL) return;
	}
    (*env)->SetByteArrayRegion(env, ctx->jcaps, 
			       0, sizeof(JVMP_SecurityCap), 
			       (jbyte*)(ctx->caps).bits);    
}

static char* createURL(char* proto, char* dir, char* name)
{
    char *res;
    int i, len;
    char *newdir;
    
    len = strlen(dir); 
    newdir = (char*)malloc(len +1);
    for (i=0; i<=len; i++)
	{	    
	    if (dir[i] == '\\') 
	      newdir[i] = '/'; 
	    else
	      newdir[i] = dir[i];
	}
    res = (char*)malloc(strlen(proto)+strlen(newdir)+strlen(name)+5);
    sprintf(res, "%s:%s/%s", proto, newdir, name);
    free(newdir);
    return res;
}

/* returns JNI_TRUE if there is acceptable for reuse Java VM */
static jint JVMP_ReuseJVM(JavaVM* *pjvm)
{
  JavaVM* vm;
  jsize   nVMs;
  
  nVMs = 0;
  if (!(JVMP_JVMMethods.JNI_GetCreatedJavaVMs)) 
    return JNI_FALSE;
  JVMP_JVMMethods.JNI_GetCreatedJavaVMs(&vm, sizeof(vm), &nVMs);
  if (!nVMs) return JNI_FALSE;
  *pjvm = vm;
  return JNI_TRUE;
}

static char* getPluginLibPath(char* jdkhome, char* wfhome) {
  static char* result = NULL;
  
  if (result != NULL) return result;
  result = (char*) malloc(strlen(wfhome) + strlen(jdkhome)+50);
  /* FIXME !! */
  sprintf(result,
#ifdef _JVMP_SUNJVM
	  "%s"PATH_SEPARATOR"%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"lib"FILE_SEPARATOR""ARCH
#endif
#ifdef _JVMP_IBMJVM
	  "%s"PATH_SEPARATOR"%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"bin"
#endif
	, wfhome, jdkhome);
  return result;
}  

static char* getWFhome()
{
#ifdef XP_WIN32
  struct _stat sbuf;
#else
  struct stat sbuf;
#endif
   /* find out WFHOME */
  JVMP_plugin_home = getenv("WFHOME");
  if (JVMP_plugin_home == NULL) 
    {
      PLUGIN_LOG("Env variable WFHOME not set");
      return NULL;
    }
#ifdef XP_WIN32
  if ((_stat(JVMP_plugin_home, &sbuf) < 0) || !((sbuf.st_mode) & _S_IFDIR)) 
#else
  if ((stat(JVMP_plugin_home, &sbuf) < 0) || !S_ISDIR(sbuf.st_mode)) 
#endif
    {
      PLUGIN_LOG2("Bad value %s of WFHOME", JVMP_plugin_home);
      return NULL;
    }
  return JVMP_plugin_home;
}

static char* getJDKhome()
{
  JDK_home = getenv("WFJDKHOME");
  if (JDK_home == NULL) 
    {
      PLUGIN_LOG("Env variable JDKHOME not set");
      return NULL;
    }
  return JDK_home;
}



static jint loadJVM(const char* name) {
  dll_t handle;
  char *libpath, *newlibpath, *pluginlibpath;
  char *libjvm, *java_home, *jre_home, *libhpi;
  int   tmplen;
  static int loaded = 0;

  typedef jint (JNICALL *JNI_GetDefaultJavaVMInitArgs_t)(void *args);
  typedef jint (JNICALL *JNI_CreateJavaVM_t)(JavaVM **pvm, void **penv, void *args);
  typedef jint (JNICALL *JNI_GetCreatedJavaVMs_t)(JavaVM **, jsize, jsize *);
  typedef jint (JNICALL *JNI_OnLoad_t)(JavaVM *vm, void *reserved);
  typedef void (JNICALL *JNI_OnUnload_t)(JavaVM *vm, void *reserved);
#define JVM_RESOLVE(method) JVMP_JVMMethods.##method = \
    (method##_t) JVMP_FindSymbol(handle, #method); \
  if (JVMP_JVMMethods.##method == NULL)  { \
    PLUGIN_LOG2("FindSymbol: %s", JVMP_LastDLLErrorString()); \
    return JNI_FALSE; \
  }
  
  if (loaded) return JNI_TRUE;
  /* bzero() ? */
  memset(&JVMP_JVMMethods, 0, sizeof(JVMP_JVMMethods)); 
  if (!getWFhome()) return JNI_FALSE;
  if (!getJDKhome()) return JNI_FALSE;
  /* LD_LIBRARY_PATH correction */
  pluginlibpath = getPluginLibPath(JDK_home, JVMP_plugin_home);
  /* pretty much useless on Win32 :) */
  libpath =  getenv("LD_LIBRARY_PATH");
  if (!libpath) libpath = "";
  tmplen = strlen(libpath) + strlen(pluginlibpath);
  global_libpath =  (char*) malloc(tmplen + 2);
  newlibpath = (char*) malloc(tmplen + 22);
  
  sprintf(global_libpath, "%s:%s", pluginlibpath, libpath);
  free(pluginlibpath);
  sprintf(newlibpath, "LD_LIBRARY_PATH=%s", global_libpath);
  putenv(newlibpath);
  java_home = (char*) malloc(strlen(JDK_home)+40);
  jre_home =  (char*) malloc(strlen(JDK_home)+40);
  libjvm = (char*) malloc(strlen(JDK_home)+strlen(name)+50);
#ifdef _JVMP_IBMJVM
  sprintf(java_home, 
	  "JAVAHOME=%s"FILE_SEPARATOR"jre", JDK_home);
  sprintf(jre_home, 
	  "JREHOME=%s"FILE_SEPARATOR"jre", JDK_home);
  sprintf(libjvm, 
	  "%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"bin"FILE_SEPARATOR""JVMKIND""FILE_SEPARATOR"%s", JDK_home,
	  name);
#endif
#ifdef _JVMP_SUNJVM
  sprintf(java_home, 
	  "JAVA_HOME=%s"FILE_SEPARATOR"jre", JDK_home);
  sprintf(jre_home, 
	  "JREHOME=%s"FILE_SEPARATOR"jre", JDK_home);
#ifdef XP_UNIX
  sprintf(libjvm, "%s/jre/lib/"ARCH"/"JVMKIND"/%s", JDK_home,
	  name);
  /* this stuff meaningful for strange platforms like Sparc Linux
     where libjvm.so uses libhpi.so, but doesn't depends on it */
  libhpi = (char*) malloc(strlen(JDK_home)+60);
  sprintf(libhpi, 
          "%s/jre/lib/"ARCH"/native_threads/libhpi.so", 
          JDK_home
          );
  /* we don't care about possible errors - just try to load libjvm.so later */
  dlopen(libhpi, RTLD_GLOBAL | RTLD_NOW);
  free (libhpi);
#else
  sprintf(libjvm, 
	  "%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"bin"FILE_SEPARATOR""JVMKIND""FILE_SEPARATOR"%s", 
	  JDK_home, name);
#endif

#endif
  putenv(java_home);
  putenv(jre_home);
  /* putenv behaves differently in different cases, so safer not to free it */
  /*
    free(java_home);
    free(jre_home);
    free(newlibpath); */
  /* PLUGIN_LOG2("loading JVM from %s", libjvm); */
  handle = JVMP_LoadDLL (libjvm, DL_MODE);
  free (libjvm);
  if (!handle) {
    PLUGIN_LOG2("LoadDLL: %s", JVMP_LastDLLErrorString());
    return JNI_FALSE;
  }  
  JVM_RESOLVE(JNI_GetDefaultJavaVMInitArgs);
  JVM_RESOLVE(JNI_CreateJavaVM);
  JVM_RESOLVE(JNI_GetCreatedJavaVMs);
  loaded = 1;
  return JNI_TRUE;
}

/* join Plugin's classpaths to the arguments passed to JVM */
static jint JVMP_initClassPath(void* *pargs) {
  JavaVMInitArgs* opt;
  char *classpath, *newclasspath, *pluginclasspath;
  char *bootclasspath, *policypath, *jvmphome, *policyurl;
  char *java_libpath, *java_home;
  char* str;
  int  i;
  
  if (!pargs) return JNI_FALSE;
  opt = (JavaVMInitArgs*) *pargs;
  if (opt == NULL) 
      {
	  opt = (JavaVMInitArgs*)malloc(sizeof(JavaVMInitArgs));
	  memset(opt, 0, sizeof(JavaVMInitArgs));
	  opt->version = DEFAULT_JNI_VERSION;
      }
  for (i=0; i < (int)opt->nOptions; i++) 
    PLUGIN_LOG3("before option[%d]=%s", i, opt->options[i].optionString);
  
  /* add javaplugin classpath to standard classpath */
  classpath = getenv("CLASSPATH");
  if (!classpath) classpath = "";
  pluginclasspath = (char*) malloc(2*strlen(JVMP_plugin_home)+50);
  sprintf(pluginclasspath, 
	  "%s"FILE_SEPARATOR"classes"PATH_SEPARATOR"%s"FILE_SEPARATOR"wf.jar"	  
	  , JVMP_plugin_home, JVMP_plugin_home);
  newclasspath = (char*) malloc(strlen(classpath) + 
				strlen(pluginclasspath) + 1);
  strcpy(newclasspath, pluginclasspath);
  if (classpath != NULL) {
    strcat(newclasspath, PATH_SEPARATOR);
    strcat(newclasspath, classpath);
  }
  str = (char*) malloc(strlen(newclasspath)+30);
  sprintf(str, "-Djava.class.path=%s", newclasspath);
  if (!JVMP_addOption(opt, str, NULL)) return JNI_FALSE; 

  java_libpath = (char*) malloc(strlen(global_libpath) + 30);
  sprintf(java_libpath, "-Djava.library.path=%s", global_libpath);
  if (!JVMP_addOption(opt, java_libpath, NULL)) return JNI_FALSE; 

  java_home = (char*) malloc(strlen(JVMP_plugin_home)+30);
  sprintf(java_home, "-Djava.home=%s"FILE_SEPARATOR"jre", JDK_home);
  if (!JVMP_addOption(opt, java_home, NULL)) return JNI_FALSE;
  free(pluginclasspath);

  bootclasspath = (char*) malloc(3*strlen(JVMP_plugin_home)+180); 
  sprintf(bootclasspath, "-Xbootclasspath:"
	  "%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"classes"PATH_SEPARATOR
	  "%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"lib"FILE_SEPARATOR"rt.jar"PATH_SEPARATOR
  	  "%s"FILE_SEPARATOR"jre"FILE_SEPARATOR"lib"FILE_SEPARATOR"i18n.jar",
	  JDK_home, JDK_home, JDK_home
  	  );
  if (!JVMP_addOption(opt, bootclasspath, NULL)) 
    return JNI_FALSE;

  if (!JVMP_addOption(opt, "-Djava.security.manager", NULL)) 
    return JNI_FALSE;

  jvmphome = (char*) malloc(strlen(JVMP_plugin_home)+20);
  sprintf(jvmphome, "-Djvmp.home=%s", JVMP_plugin_home);
  if (!JVMP_addOption( opt, jvmphome, NULL)) 
    return JNI_FALSE;

  policyurl = createURL("file", JVMP_plugin_home, "plugin.policy");
  policypath = (char*) malloc(strlen(policyurl)+30);
  sprintf(policypath, "-Djava.security.policy=%s", policyurl);
  free(policyurl);
  if (!JVMP_addOption( opt, policypath, NULL)) 
    return JNI_FALSE;

  for (i=0; i < (int)opt->nOptions; i++)  
    PLUGIN_LOG3("JVM option[%d]=%s", i, opt->options[i].optionString);  
  *pargs = opt;
  return JNI_TRUE;
}


static jint JNICALL JVMP_initJavaClasses(JavaVM* jvm, JNIEnv* env) {
  jclass clz;
  jmethodID meth;
  jobject   inst;
  jstring   jhome;

#ifdef XP_WIN32
#define WF_MAIN_CLASS "sun/jvmp/generic/win32/Plugin"
#endif
#ifdef XP_UNIX
#define WF_MAIN_CLASS "sun/jvmp/generic/motif/Plugin"
#endif

  (*env)->ExceptionClear(env);
  /* Find main plugin's class */
  
  clz = (*env)->FindClass(env, WF_MAIN_CLASS);
  if (clz ==  NULL) {
    (*env)->ExceptionDescribe(env);
    (*env)->ExceptionClear(env);
    return JNI_FALSE;
  }
  jhome = (*env)->NewStringUTF(env, JVMP_plugin_home);
  
  /* Find its "startJVM" method */
  meth = (*env)->GetStaticMethodID(env, clz, "startJVM", 
				   "(Ljava/lang/String;I)Lsun/jvmp/PluggableJVM;");
  if ((*env)->ExceptionOccurred(env)) {
    (*env)->ExceptionDescribe(env);
    (*env)->ExceptionClear(env);
    return JNI_FALSE;
  };
  inst = (*env)->CallStaticObjectMethod(env, clz, meth, jhome, DEBUG_LEVEL);
  if ((*env)->ExceptionOccurred(env)) {      
    (*env)->ExceptionDescribe(env);
    (*env)->ExceptionClear(env);
    return JNI_FALSE;
  };
  JVMP_PluginInstance = (*env)->NewWeakGlobalRef(env, inst);
  JVMP_PluginClass = (*env)->NewWeakGlobalRef(env, clz);
  return JNI_TRUE;
}

/* prototype for this function */
static jint JNICALL JVMP_GetCallingContext(JVMP_CallingContext **pctx);
static jint JVMP_SetCap(JVMP_CallingContext *ctx, jint cap_no)
{
  JVMP_ALLOW_CAP(ctx->caps, cap_no);
  return JNI_TRUE;
}

static jint JVMP_SetCaps(JVMP_CallingContext *ctx,
			 JVMP_SecurityCap *new_caps)
{
  if (!new_caps) return JNI_FALSE;
  memcpy(&(ctx->caps), new_caps, sizeof(JVMP_SecurityCap));
  return JNI_TRUE;
}

static jint JVMP_GetCap(JVMP_CallingContext *ctx, jint cap_no)
{
  return JVMP_IS_CAP_ALLOWED(ctx->caps, cap_no) ? JNI_TRUE : JNI_FALSE;
}



static jint JVMP_IsActionAllowed(JVMP_CallingContext *ctx,
				 JVMP_SecurityAction *caps)
{
  int i, r; 
  for(i=0, r=1; i < JVMP_MAX_CAPS_BYTES; i++)
    {
      r = 
	r && ((~(caps->bits)[i]) |
	      (((ctx->caps).bits)[i] & (caps->bits)[i]));
      if (!r) return JNI_FALSE;
    }
  return JNI_TRUE;
} 
static jint JNICALL JVMP_AttachCurrentThread(JVMP_CallingContext* ctx, 
					     jint *pID);
static jint JNICALL JVMP_DetachCurrentThread(JVMP_CallingContext* ctx);
static JVMP_CallingContext* NewCallingContext()
{
  JVMP_CallingContext* ctx_new = NULL;
  jint tid;

  ctx_new = (JVMP_CallingContext *) malloc(sizeof(JVMP_CallingContext));
  if (!ctx_new) return NULL;
  ctx_new->This = ctx_new;
  ctx_new->source_thread = THIS_THREAD();
  ctx_new->env = NULL;
  ctx_new->data = NULL;
  ctx_new->err = JVMP_ERROR_NOERROR;
  ctx_new->source_thread->ctx = ctx_new;

  JVMP_FORBID_ALL_CAPS(ctx_new->caps);
  /* basic capabilities of every calling context */
  JVMP_ALLOW_CAP(ctx_new->caps, JVMP_CAP_SYS_PARITY);
  JVMP_ALLOW_CAP(ctx_new->caps, JVMP_CAP_SYS_CHCAP);
  /* nothing useful here, as no JNIEnv yet. Later call UpdateCaps() */
  ctx_new->jcaps = NULL;
  ctx_new->AllowCap = &JVMP_SetCap;
  ctx_new->GetCap = &JVMP_GetCap;
  ctx_new->SetCaps = &JVMP_SetCaps;  
  ctx_new->IsActionAllowed = &JVMP_IsActionAllowed;
  if (JVMP_JVM_running != NULL) JVMP_AttachCurrentThread(ctx_new, &tid);
  return ctx_new;
}
static void DestroyCallingContext(JVMP_CallingContext* ctx)
{  
  free(ctx);
}
static jint ValidCtx(JVMP_CallingContext* ctx)
{
  JVMP_Thread* thr;
  if (!ctx) return JNI_FALSE;
  thr = THIS_THREAD();
  if (ctx->source_thread != thr) 
    {
      PLUGIN_LOG("USED FROM INCORRECT THREAD. JVMP_CallingContext is per-thread structure");
      return JNI_FALSE;
    }
  /* clean up errors in this context */
  ctx->err = JVMP_ERROR_NOERROR;
  return JNI_TRUE;
}

static jint JNICALL JVMP_GetDescription(JVMP_PluginDescription* *pdesc)
{
  if (!pdesc) return JNI_FALSE;
  *pdesc = &JVMP_Description;
  return JNI_TRUE;
}

/* arguments are taken into account only if JVM isn't already created */
static jint JNICALL JVMP_GetRunningJVM(JVMP_CallingContext **pctx, 
				       void* args,
				       jint allow_reuse) 
{
  JavaVM* jvm_new = NULL;
  JVMP_CallingContext* ctx_new = NULL;
  JNIEnv* env_new = NULL;
  jint jvm_err, tid;
  
  MUTEX_LOCK(jvm_mutex);
  if (JVMP_JVM_running == NULL) 
  {
    if (!JVMP_initClassPath(&args)) return JNI_FALSE;
    ctx_new = NewCallingContext();
    if (!ctx_new) return JNI_FALSE;
     /* let's start with attempt to find out is Java VM already loaded */
    if (allow_reuse && JVMP_ReuseJVM(&jvm_new))
      {
	(*jvm_new)->GetEnv(jvm_new, (void**)&(ctx_new->env), DEFAULT_JNI_VERSION);
      }
    else if ((jvm_err = JVMP_JVMMethods.JNI_CreateJavaVM
	      (&jvm_new, (void**)&(ctx_new->env), args)) != JNI_OK)  
      {
	PLUGIN_LOG2("JVMP: JNI_CreateJavaVM failed due %d", (int)jvm_err);
	MUTEX_UNLOCK(jvm_mutex);
	return jvm_err;
      }
    
    env_new = ctx_new->env;
    JVMP_JVM_running = jvm_new;
    JVMP_PluginContext.jvm = jvm_new;
  } else {
    /* XXX: FIXME version */
    MUTEX_UNLOCK(jvm_mutex);
    return JVMP_GetCallingContext(pctx);
  };
    
  if (env_new) {
    if (!JVMP_initJavaClasses(jvm_new, env_new)) {
      PLUGIN_LOG("Problems with Java classes of JVMP. Cannot continue.");
      JVMP_JVM_running = NULL;
      MUTEX_UNLOCK(jvm_mutex);
      return JNI_FALSE;
    };
    (*pctx) = ctx_new;
    (*env_new)->ExceptionClear(env_new);
     /* this call from NewCallingContext() not executed, as JVM was NULL */
    JVMP_AttachCurrentThread(ctx_new, &tid);
    MUTEX_UNLOCK(jvm_mutex);
    return JNI_TRUE;  
  }
  else {
    MUTEX_UNLOCK(jvm_mutex);
    return JNI_FALSE; 
  };
}
static jint JNICALL JVMP_SendSysEvent(JVMP_CallingContext* ctx,
				      jint   event,
				      jlong  data,
				      jint   priority)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  
  
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSEVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "sendSysEvent", "([BIJI)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, event, data, priority);
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      }
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}

static jint JNICALL JVMP_PostSysEvent(JVMP_CallingContext* ctx,
				      jint   event,
				      jlong  data,
				      jint   priority)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  
  
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSEVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "postSysEvent", "([BIJI)I");
	  if (myMethod == NULL) 
	      {      		
		  (*env)->ExceptionDescribe(env);		  
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, event, data, priority);
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);	  
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      }
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}

static jint JNICALL JVMP_StopJVM(JVMP_CallingContext* ctx) {
    static  jmethodID myMethod=NULL;
    JNIEnv* env;
    jint    res;

    if (!ValidCtx(ctx)) return JNI_FALSE;
    if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
	{
	    ctx->err = JVMP_ERROR_NOACCESS;
	    return JNI_FALSE;
	}
    env = ctx->env;
    if (JVMP_JVM_running == NULL) return JNI_TRUE;
    if (!myMethod)
	{
	    myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					   "stopJVM", "([B)I");
	    if (myMethod == NULL) {      
		(*env)->ExceptionDescribe(env);
		(*env)->ExceptionClear(env);
		ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		return JNI_FALSE;
	    }
	}
    UpdateCaps(ctx);
    res = (*env)->CallIntMethod(env, JVMP_PluginInstance, 
				myMethod, ctx->jcaps);    
    if ((*env)->ExceptionOccurred(env)) {      
      (*env)->ExceptionDescribe(env);
      (*env)->ExceptionClear(env);
      ctx->err = JVMP_ERROR_JAVAEXCEPTION;
      return JNI_FALSE;
    }
    /* if stopJVM returns > 0, it means there are some not unregistered
       extensions, so not try to kill JVM */
    if (res > 0) return JNI_TRUE;
    /* FIXME: I don't know how to completly destroy JVM.
       Current code waits until only one thread left - but how to kill 'em?
       Host application should be able to control such stuff as execution of 
       JVM, even if there are some java threads running
     */
    (*JVMP_JVM_running)->DestroyJavaVM(JVMP_JVM_running);
    JVMP_JVM_running = NULL;
    return JNI_TRUE;
}

static jint JNICALL JVMP_RegisterWindow(JVMP_CallingContext* ctx, 
					JVMP_DrawingSurfaceInfo *win, 
					jint *pID) 
{
    jint handle;
    jint ID=0;
    static jmethodID myMethod=NULL;
    JNIEnv* env;
    
    if (!ValidCtx(ctx)) return JNI_FALSE;
    if (win == NULL) return JNI_FALSE;
    if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
	{
	    ctx->err = JVMP_ERROR_NOACCESS;
	    return JNI_FALSE;
	}
    env = ctx->env;
    handle = (jint)(win->window);
    if (!myMethod)
	{
	    myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					   "RegisterWindow", "(III)I");
	    if (myMethod == NULL) 
		{      
		    (*env)->ExceptionDescribe(env);
		    (*env)->ExceptionClear(env);
		    ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		    return JNI_FALSE;
		}
	}
    ID = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
			       handle, win->width, win->height);    
    if ((*env)->ExceptionOccurred(env))
      {      
	(*env)->ExceptionDescribe(env);
	(*env)->ExceptionClear(env);
	ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	return JNI_FALSE;
      }
    if (ID == 0) 
      { 
	ctx->err = JVMP_ERROR_METHODFAILED;
	return JNI_FALSE; 
      };
    if (pID) *pID = ID;
    return JNI_TRUE;
}

static jint JNICALL JVMP_UnregisterWindow(JVMP_CallingContext* ctx, 
					  jint ID) 
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  if (ID <= 0) 
    {
      ctx->err = JVMP_ERROR_INVALIDARGS;
      return JNI_FALSE;
    }
  env = ctx->env;
  if (!myMethod) 
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "UnregisterWindow", "(I)I");
	  if (myMethod == NULL) {      
	      (*env)->ExceptionDescribe(env);
	      (*env)->ExceptionClear(env);
	      ctx->err = JVMP_ERROR_NOSUCHMETHOD;
	      return JNI_FALSE;
	  }
      }
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ID);    
  if ((*env)->ExceptionOccurred(env)) 
    {      
      (*env)->ExceptionDescribe(env);
      (*env)->ExceptionClear(env);
      ctx->err = JVMP_ERROR_JAVAEXCEPTION;
      return JNI_FALSE;
    };
  if (result != ID) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}

static jint JNICALL JVMP_GetCallingContext(JVMP_CallingContext* *pctx)
{
  jint ret;
  JVMP_CallingContext* ctx_new;
  JVMP_Thread*  thr = THIS_THREAD();

  if (thr->ctx) 
    {
      *pctx = thr->ctx;
      return JNI_TRUE;
    }
  ctx_new = NewCallingContext();
  if (!ctx_new) return JNI_FALSE;
  ret = (*JVMP_JVM_running)->GetEnv(JVMP_JVM_running, 
				    (void**)&(ctx_new->env), 
				    DEFAULT_JNI_VERSION);
  *pctx = ctx_new;
  return (ret == JNI_OK ? JNI_TRUE : JNI_FALSE);
}
/* registers native object, that consist of pair 
   mutex/condvar and can be used to synchronize execution of Java and
   native threads.
*/
static jint JNICALL JVMP_RegisterMonitorObject(JVMP_CallingContext* ctx, 
					       JVMP_MonitorInfo *monitor, 
					       jint *pID) 
{  
  jint ID=0;
  static jmethodID myMethod=NULL;
  JNIEnv* env;
  
  if (!ValidCtx(ctx)) return JNI_FALSE;
  /* we can register only correctly
     (using JVMP_MONITOR_INITIALIZER macros) initialized monitors */
  if ((monitor == NULL) || (monitor->monitor_state != JVMP_STATE_NOTINITED) ||
      (monitor->mutex_state != JVMP_STATE_NOTINITED))
      return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
    {
      ctx->err = JVMP_ERROR_NOACCESS;
      return JNI_FALSE;
    }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "RegisterMonitorObject", "(J)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  monitor->owner = THIS_THREAD();
  ID = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
			     PTR_TO_JLONG(monitor));    
  if ((*env)->ExceptionOccurred(env))
    {      
      (*env)->ExceptionDescribe(env);
      (*env)->ExceptionClear(env);
      ctx->err = JVMP_ERROR_JAVAEXCEPTION;
      return JNI_FALSE;
    }
  if (ID == 0) return JNI_FALSE;
  monitor->id = ID;
  monitor->monitor_state = JVMP_STATE_INITED;
  monitor->mutex_state   = JVMP_STATE_INITED;
  if (pID) *pID = ID;
  return JNI_TRUE;
}

static jint JNICALL JVMP_UnregisterMonitorObject(JVMP_CallingContext* ctx, 
						 jint ID) 
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ID <= 0) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "UnregisterMonitorObject", "(I)I");
	  if (myMethod == NULL) {      
	      (*env)->ExceptionDescribe(env);
	      (*env)->ExceptionClear(env);
	      ctx->err = JVMP_ERROR_NOSUCHMETHOD;
	      return JNI_FALSE;
	  }
      }
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ID);    
  if ((*env)->ExceptionOccurred(env)) 
    {      
      (*env)->ExceptionDescribe(env);
      (*env)->ExceptionClear(env);
      ctx->err = JVMP_ERROR_JAVAEXCEPTION;
      return JNI_FALSE;
    };
  if (result != ID) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}

static jint JNICALL JVMP_AttachCurrentThread(JVMP_CallingContext* ctx, 
					     jint *pID)
{
  static jmethodID myMethod = NULL;
  jint result = 0;
  JNIEnv* env;
  
  /* 
     to prevent people from using this method incorrectly - GetCallingContext
     should be used instead
  */
  if (!ValidCtx(ctx)) return JNI_FALSE;
  result = (*JVMP_JVM_running)->AttachCurrentThread(JVMP_JVM_running, 
						    (void**)&(ctx->env), NULL);
  env = ctx->env;
  if (result != JNI_OK) return JNI_FALSE;
  
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "attachThread", "()I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod);    
  if ((*env)->ExceptionOccurred(env)) 
      {      
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  ctx->source_thread->id = result;
  *pID = result;
  return JNI_TRUE;
}

static jint JNICALL JVMP_DetachCurrentThread(JVMP_CallingContext* ctx)
{
  static jmethodID myMethod = NULL;
  jint result = 0;
  JNIEnv* env;
  
  if (!ValidCtx(ctx)) return JNI_FALSE;
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "detachThread", "()I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod);    
  if ((*env)->ExceptionOccurred(env)) 
      {      
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      }
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  (*JVMP_JVM_running)->DetachCurrentThread(JVMP_JVM_running);
  if (ctx->source_thread->cleanup) 
      ctx->source_thread->cleanup(ctx->source_thread);
  DestroyCallingContext(ctx);
  return JNI_TRUE;
}

static jint JNICALL JVMP_CreatePeer(JVMP_CallingContext* ctx,
				    const char*          cid,
				    jint                 version,
				    jint                 *target)
{
  static jmethodID myMethod = NULL;
  jint             result = 0;
  JNIEnv*          env = NULL;
  jstring          jcid = NULL;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_EVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "createPeer", "([BLjava/lang/String;I)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  jcid = (*env)->NewStringUTF(env, cid);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, jcid, version);    
  if ((*env)->ExceptionOccurred(env)) 
      {      
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      }
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  *target = result;
  return JNI_TRUE;
}

static jint JNICALL JVMP_SendEvent(JVMP_CallingContext* ctx,
				   jint   target,
				   jint   event,
				   jlong  data,
				   jint   priority)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  
  
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_EVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "sendEvent", "([BIIJI)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      };
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, target, event, data, priority);
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      }
  return result;
}

static jint JNICALL JVMP_PostEvent(JVMP_CallingContext* ctx,
				   jint   target,
				   jint   event,
				   jlong  data,
				   jint   priority)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_EVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "postEvent", "([BIIJI)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, target, event, data, priority);  
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}

static jint JNICALL JVMP_DestroyPeer(JVMP_CallingContext* ctx,
				     jint   target)
{
  
  jmethodID myMethod = NULL;
  jint result = 0;
  JNIEnv* env;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_EVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env  = ctx->env;
  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
				 "destroyPeer", "([BI)I");
  if (myMethod == NULL) 
      {      
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
	  return JNI_FALSE;
      };
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, target);    
  if ((*env)->ExceptionOccurred(env)) 
      {      
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  if (result != target) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}


static jint JNICALL JVMP_RegisterExtension(JVMP_CallingContext* ctx,
					   const char* extPath,
					   jint *pID,
					   jlong data)
{
    dll_t handle;
    JVMP_GetExtension_t JVMP_GetExtension;
    JVMP_Extension* ext;
    const char*   cid=NULL;
    jint version, result=0;
    static jmethodID myMethod = NULL;
    char *classpath, *classname;
    jstring jclasspath=NULL, jclassname=NULL, jcid=NULL;
    JNIEnv* env;
    JVMP_SecurityCap caps, sh_mask;
    jbyteArray jcaps, jmask;
 
    if (!ValidCtx(ctx)) return JNI_FALSE;
    if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
    env = ctx->env;
    handle = JVMP_LoadDLL(extPath, DL_MODE);
    if (!handle) {
	PLUGIN_LOG2("extension LoadDLL: %s", JVMP_LastDLLErrorString());
	return JNI_FALSE;
    };
    JVMP_GetExtension = 
	(JVMP_GetExtension_t)JVMP_FindSymbol(handle, "JVMP_GetExtension");
    if (JVMP_GetExtension == NULL)
	{
	    PLUGIN_LOG2("extension FindSymbol: %s", JVMP_LastDLLErrorString());
	    return JNI_FALSE;
	}
    /* to implement JVMP_UnregisterExtension we should support
       GHashTable of opened extensions, but later */
    if (((*JVMP_GetExtension)(&ext) != JNI_TRUE))
	{
	    PLUGIN_LOG2("Cannot obtain extension from %s", extPath);
	    ctx->err = JVMP_ERROR_INITFAILED;
	    return JNI_FALSE;
	}
    (ext->JVMPExt_GetExtInfo)(&cid, &version, NULL);
    if ((ext->JVMPExt_Init)(0) != JNI_TRUE) 
      {
	ctx->err = JVMP_ERROR_INITFAILED;
	return JNI_FALSE;
      }
    /* get capability range reserved by this extension */ 
    if ((ext->JVMPExt_GetCapsRange)(&caps, &sh_mask) != JNI_TRUE) 
      {
	ctx->err = JVMP_ERROR_INITFAILED;
	return JNI_FALSE;
      }
    jcaps = (*env)->NewByteArray(env, sizeof(JVMP_SecurityCap));
    if (jcaps == NULL) return JNI_FALSE;
    (*env)->SetByteArrayRegion(env, jcaps, 
			       0, sizeof(JVMP_SecurityCap), (jbyte*)caps.bits);
    jmask = (*env)->NewByteArray(env, sizeof(JVMP_SecurityCap));
    if (jmask == NULL) return JNI_FALSE;
    (*env)->SetByteArrayRegion(env, jmask, 
			       0, sizeof(JVMP_SecurityCap), 
			       (jbyte*)sh_mask.bits);
    if (!myMethod)
	{
	    myMethod = 
		(*env)->GetMethodID(env, JVMP_PluginClass, 
				    "registerExtension",
				    "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;[B[BJ)I");
	    if (myMethod == NULL) 
		{
		    (*env)->ExceptionDescribe(env);
		    (*env)->ExceptionClear(env);
		    ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		    return JNI_FALSE;
		}
	}
    
    (ext->JVMPExt_GetBootstrapClass)(&classpath, &classname);
    jclasspath = (*env)->NewStringUTF(env, classpath);
    jclassname = (*env)->NewStringUTF(env, classname);
    jcid       = (*env)->NewStringUTF(env, cid);
    if (jclasspath == NULL || jclassname == NULL || jcid == NULL) 
	{
	    ctx->err = JVMP_ERROR_INITFAILED;
	    return JNI_FALSE;
       }
    
     result = (*env)->CallIntMethod(env, 
				    JVMP_PluginInstance,
				    myMethod, 
				    jcid, 
				    version, 
				    jclasspath,
				    jclassname, 
				    jcaps, 
				    jmask,
				    data);
     
     /* XXX: maybe leak of jclasspath, jclassname, jcaps - ReleaseStringUTFChars*/
     if ((*env)->ExceptionOccurred(env)) 
	 {      
	     (*env)->ExceptionDescribe(env);
	     (*env)->ExceptionClear(env);
	     ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	     return JNI_FALSE;
	 }
     *pID = result;
     return JNI_TRUE;
}

static jint JNICALL JVMP_SendExtensionEvent(JVMP_CallingContext* ctx,
					    jint   target,
					    jint   event,
					    jlong  data,
					    jint   priority)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  
  
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_EVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "sendExtensionEvent", "([BIIJI)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, target, event, data, priority); 
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}

static jint JNICALL JVMP_PostExtensionEvent(JVMP_CallingContext* ctx,
					    jint   target,
					    jint   event,
					    jlong  data,
					    jint   priority)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_EVENT) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "postExtensionEvent", "([BIIJI)I");
	  
	  if (myMethod == NULL)
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      };
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, target, event, data, priority);  
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  if (result == 0) 
    {
      ctx->err = JVMP_ERROR_METHODFAILED;
      return JNI_FALSE;
    }
  return JNI_TRUE;
}


static jint JNICALL JVMP_DirectPeerCall(JVMP_CallingContext* ctx,
					jint                 target,
					jint                 arg1,
					jlong                arg2)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_CALL) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "handleCall", "([BIIIJ)I");
	  if (myMethod == NULL)
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, 1, target, arg1, arg2);  
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  return result;
}

static jint JNICALL JVMP_DirectExtCall(JVMP_CallingContext*  ctx,
				       jint                  target,
				       jint                  arg1,
				       jlong                 arg2)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_CALL) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "handleCall", "([BIIIJ)I");
	  if (myMethod == NULL)
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, 2, target, arg1, arg2);  
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  return result;
}

static jint JNICALL JVMP_DirectJVMCall(JVMP_CallingContext*    ctx,
				       jint                    arg1,
				       jlong                   arg2)
{
  static jmethodID myMethod=NULL;
  jint result;
  JNIEnv* env; 
  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSCALL) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "handleCall", "([BIIJ)I");
	  if (myMethod == NULL)
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, 3, arg1, arg2);  
  if ((*env)->ExceptionOccurred(env)) 
      {
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  return result;
}

static jint JNICALL JVMP_UnregisterExtension(JVMP_CallingContext* ctx,
					     jint ID)
{
  jint result;
  JNIEnv* env;
  static jmethodID myMethod = NULL;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_SYSTEM) != JNI_TRUE)
    {
      ctx->err = JVMP_ERROR_NOACCESS;
      return JNI_FALSE;
    }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = (*env)->GetMethodID(env, JVMP_PluginClass, 
					 "unregisterExtension", "([BI)I");
	  if (myMethod == NULL)
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  UpdateCaps(ctx);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, myMethod, 
				 ctx->jcaps, ID);  
  if ((*env)->ExceptionOccurred(env)) 
    {
      (*env)->ExceptionDescribe(env);
      (*env)->ExceptionClear(env);
      ctx->err = JVMP_ERROR_JAVAEXCEPTION;
      return JNI_FALSE;
    }
  return result;
}

static jint JNICALL JVMP_EnableCapabilities(JVMP_CallingContext* ctx,
					    JVMP_SecurityCap*    caps,
					    jint                 num_principals,
					    jint*                principals_len,
					    jbyte*               *principals)
{
  JNIEnv* env;
  static jmethodID myMethod = NULL;
  jbyteArray jcaps = NULL, jprinc;
  jobjectArray  jprincipals = NULL;
  jint result, i;
  jclass byteArrayClass;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_CHCAP) != JNI_TRUE)
      {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env = ctx->env;
  if (!myMethod)
      {
	  myMethod = 
	      (*env)->GetMethodID(env, JVMP_PluginClass, 
				  "enableCapabilities",
				  "([B[[B)I");
	  if (myMethod == NULL) 
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
  byteArrayClass = (*env)->FindClass(env, "[B");
  jprincipals = (*env)->NewObjectArray(env, num_principals, 
				       byteArrayClass, NULL);
  if (jprincipals == NULL) return JNI_FALSE;
  for (i=0; i < num_principals; i++)
      {
	  jprinc = (*env)->NewByteArray(env, principals_len[i]);
	  if (jprinc == NULL) return JNI_FALSE;
	  (*env)->SetByteArrayRegion(env, jprinc, 
				     0, principals_len[i], 
				     (jbyte*)principals[i]);
	  (*env)->SetObjectArrayElement(env, jprincipals, i,  jprinc);
      }   
  jcaps = (*env)->NewByteArray(env, sizeof(JVMP_SecurityCap));
  if (jcaps == NULL) return JNI_FALSE;
  (*env)->SetByteArrayRegion(env, jcaps, 
			     0, sizeof(JVMP_SecurityCap), (jbyte*)caps->bits);
  result = (*env)->CallIntMethod(env, JVMP_PluginInstance, 
				 myMethod, 
				 jcaps,
				 jprincipals);
  if ((*env)->ExceptionOccurred(env)) 
      {      
	  (*env)->ExceptionDescribe(env);
	  (*env)->ExceptionClear(env);
	  ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	  return JNI_FALSE;
      };
  /* if Java side doesn't allow those capabilities */
  if (result != 1) 
    {
      ctx->err =  JVMP_ERROR_CAPSNOTGIVEN;
      return JNI_FALSE;
    }
  /* update current context capabilities */
  JVMP_SET_CAPS(ctx->caps, *caps);
  return JNI_TRUE;
}

static jint JNICALL JVMP_DisableCapabilities(JVMP_CallingContext* ctx,
					     JVMP_SecurityCap* caps)
{
  JNIEnv* env;
  static jmethodID myMethod = NULL;
  jobject jprincipal = NULL;
  jbyteArray jcaps = NULL;
  jint result;

  if (!ValidCtx(ctx)) return JNI_FALSE;
  if (ctx->GetCap(ctx, JVMP_CAP_SYS_CHCAP) != JNI_TRUE)
    {
	  ctx->err = JVMP_ERROR_NOACCESS;
	  return JNI_FALSE;
      }
  env  = ctx->env;
  if (!myMethod)
      {
	  myMethod = 
	      (*env)->GetMethodID(env, JVMP_PluginClass, 
				  "disableCapabilities",
				  "([B)I");
	  if (myMethod == NULL)
	      {      
		  (*env)->ExceptionDescribe(env);
		  (*env)->ExceptionClear(env);
		  ctx->err = JVMP_ERROR_NOSUCHMETHOD;
		  return JNI_FALSE;
	      }
      }
   jcaps = (*env)->NewByteArray(env, sizeof(JVMP_SecurityCap));
   if (jcaps == NULL) return JNI_FALSE;
   (*env)->SetByteArrayRegion(env, jcaps, 0, 
			      sizeof(JVMP_SecurityCap), (jbyte*)caps->bits);
   result = (*env)->CallIntMethod(env, JVMP_PluginInstance, 
				  myMethod, 
				  jcaps,
				  jprincipal);
   if ((*env)->ExceptionOccurred(env)) 
	 {      
	   (*env)->ExceptionDescribe(env);
	   (*env)->ExceptionClear(env);
	   ctx->err = JVMP_ERROR_JAVAEXCEPTION;
	   return JNI_FALSE;
	 };
   /* update current context capabilities */
   /* XXX: check for system caps */
  JVMP_RESET_CAPS(ctx->caps, *caps);
  return JNI_TRUE;
}

static jint JNICALL JVMP_GetLastErrorString(JVMP_CallingContext*  ctx,
					    char*                 buf)
{
  jint err;
  if (ctx) err=ctx->err;
  if (!ValidCtx(ctx) || !buf) return JNI_FALSE;
  strcpy(buf,  g_jvmp_errors[err]);
  return JNI_TRUE;
}
static JVMP_PluginDescription JVMP_Description = {
/* JVM vendor */
#ifdef _JVMP_SUNJVM
  "Sun",
#endif
#ifdef _JVMP_IBMJVM
  "IBM",
#endif
/* JVM version: XXX - set it correctly */
  "1.3",
/*  JVM kind */
  JVMKIND,
/* JVMP version */
  "1.0",
/* platform */
  PLATFORM,
/* arch */
  ARCH,
/* vendor specific data */
  NULL
};
/* Here main structure is initialized */

static JVMP_RuntimeContext JVMP_PluginContext = {
    NULL,
    &JVMP_GetDescription,
    &JVMP_GetRunningJVM,
    &JVMP_SendSysEvent,
    &JVMP_PostSysEvent,
    &JVMP_StopJVM,
    &JVMP_RegisterWindow, 
    &JVMP_UnregisterWindow,
    &JVMP_RegisterMonitorObject,
    &JVMP_UnregisterMonitorObject,
    &JVMP_AttachCurrentThread,
    &JVMP_DetachCurrentThread,
    &JVMP_GetCallingContext,
    &JVMP_CreatePeer,
    &JVMP_SendEvent,
    &JVMP_PostEvent,
    &JVMP_DestroyPeer,
    &JVMP_RegisterExtension,
    &JVMP_SendExtensionEvent,
    &JVMP_PostExtensionEvent,
    &JVMP_UnregisterExtension,
    &JVMP_EnableCapabilities,
    &JVMP_DisableCapabilities,
    &JVMP_DirectPeerCall,
    &JVMP_DirectExtCall,
    &JVMP_DirectJVMCall,
    &JVMP_GetLastErrorString,
    { NULL }
};

jint _JVMP_IMPORT_OR_EXPORT JNICALL JVMP_GetPlugin(JVMP_RuntimeContext** cx) {
    if (cx == NULL) return JNI_FALSE;
    if (jvm_mutex == NULL) MUTEX_CREATE(jvm_mutex);
    if (!loadJVM(LIBJVM)) { 
      PLUGIN_LOG("BAD - JVM loading failed");
      *cx = NULL;
      return JNI_FALSE;
    }
    (*cx) = &JVMP_PluginContext;
    return JNI_TRUE;
}
