summaryrefslogtreecommitdiff
path: root/Lib/java/director.swg
blob: 346cec2cc32a808422329a677894365ff65690a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/* -----------------------------------------------------------------------------
 * director.swg
 *
 * This file contains support for director classes that proxy
 * method calls from C++ to Java extensions.
 * ----------------------------------------------------------------------------- */

#ifdef __cplusplus

#if defined(DEBUG_DIRECTOR_OWNED)
#include <iostream>
#endif

#include <exception>

namespace Swig {

  /* Java object wrapper */
  class JObjectWrapper {
  public:
    JObjectWrapper() : jthis_(NULL), weak_global_(true) {
    }

    ~JObjectWrapper() {
      jthis_ = NULL;
      weak_global_ = true;
    }

    bool set(JNIEnv *jenv, jobject jobj, bool mem_own, bool weak_global) {
      if (!jthis_) {
        weak_global_ = weak_global || !mem_own; // hold as weak global if explicitly requested or not owned
        if (jobj)
          jthis_ = weak_global_ ? jenv->NewWeakGlobalRef(jobj) : jenv->NewGlobalRef(jobj);
#if defined(DEBUG_DIRECTOR_OWNED)
        std::cout << "JObjectWrapper::set(" << jobj << ", " << (weak_global ? "weak_global" : "global_ref") << ") -> " << jthis_ << std::endl;
#endif
        return true;
      } else {
#if defined(DEBUG_DIRECTOR_OWNED)
        std::cout << "JObjectWrapper::set(" << jobj << ", " << (weak_global ? "weak_global" : "global_ref") << ") -> already set" << std::endl;
#endif
        return false;
      }
    }

    jobject get(JNIEnv *jenv) const {
#if defined(DEBUG_DIRECTOR_OWNED)
      std::cout << "JObjectWrapper::get(";
      if (jthis_)
        std::cout << jthis_;
      else
        std::cout << "null";
      std::cout << ") -> return new local ref" << std::endl;
#endif
      return (jthis_ ? jenv->NewLocalRef(jthis_) : jthis_);
    }

    void release(JNIEnv *jenv) {
#if defined(DEBUG_DIRECTOR_OWNED)
      std::cout << "JObjectWrapper::release(" << jthis_ << "): " << (weak_global_ ? "weak global ref" : "global ref") << std::endl;
#endif
      if (jthis_) {
        if (weak_global_) {
          if (jenv->IsSameObject(jthis_, NULL) == JNI_FALSE)
            jenv->DeleteWeakGlobalRef((jweak)jthis_);
        } else
          jenv->DeleteGlobalRef(jthis_);
      }

      jthis_ = NULL;
      weak_global_ = true;
    }

    /* Only call peek if you know what you are doing wrt to weak/global references */
    jobject peek() {
      return jthis_;
    }

    /* Java proxy releases ownership of C++ object, C++ object is now
       responsible for destruction (creates NewGlobalRef to pin Java proxy) */
    void java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
      if (take_or_release) {  /* Java takes ownership of C++ object's lifetime. */
        if (!weak_global_) {
          jenv->DeleteGlobalRef(jthis_);
          jthis_ = jenv->NewWeakGlobalRef(jself);
          weak_global_ = true;
        }
      } else {
	/* Java releases ownership of C++ object's lifetime */
        if (weak_global_) {
          jenv->DeleteWeakGlobalRef((jweak)jthis_);
          jthis_ = jenv->NewGlobalRef(jself);
          weak_global_ = false;
        }
      }
    }

  private:
    /* pointer to Java object */
    jobject jthis_;
    /* Local or global reference flag */
    bool weak_global_;
  };

  /* director base class */
  class Director {
    /* pointer to Java virtual machine */
    JavaVM *swig_jvm_;

  protected:
#if defined (_MSC_VER) && (_MSC_VER<1300)
    class JNIEnvWrapper;
    friend class JNIEnvWrapper;
#endif
    /* Utility class for managing the JNI environment */
    class JNIEnvWrapper {
      const Director *director_;
      JNIEnv *jenv_;
      int env_status;
    public:
      JNIEnvWrapper(const Director *director) : director_(director), jenv_(0), env_status(0) {
#if defined(__ANDROID__)
        JNIEnv **jenv = &jenv_;
#else
        void **jenv = (void **)&jenv_;
#endif
        env_status = director_->swig_jvm_->GetEnv((void **)&jenv_, JNI_VERSION_1_2);
#if defined(SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON)
        // Attach a daemon thread to the JVM. Useful when the JVM should not wait for 
        // the thread to exit upon shutdown. Only for jdk-1.4 and later.
        director_->swig_jvm_->AttachCurrentThreadAsDaemon(jenv, NULL);
#else
        director_->swig_jvm_->AttachCurrentThread(jenv, NULL);
#endif
      }
      ~JNIEnvWrapper() {
#if !defined(SWIG_JAVA_NO_DETACH_CURRENT_THREAD)
        // Some JVMs, eg jdk-1.4.2 and lower on Solaris have a bug and crash with the DetachCurrentThread call.
        // However, without this call, the JVM hangs on exit when the thread was not created by the JVM and creates a memory leak.
        if (env_status == JNI_EDETACHED)
          director_->swig_jvm_->DetachCurrentThread();
#endif
      }
      JNIEnv *getJNIEnv() const {
        return jenv_;
      }
    };

    /* Java object wrapper */
    JObjectWrapper swig_self_;

    /* Disconnect director from Java object */
    void swig_disconnect_director_self(const char *disconn_method) {
      JNIEnvWrapper jnienv(this) ;
      JNIEnv *jenv = jnienv.getJNIEnv() ;
      jobject jobj = swig_self_.get(jenv);
#if defined(DEBUG_DIRECTOR_OWNED)
      std::cout << "Swig::Director::disconnect_director_self(" << jobj << ")" << std::endl;
#endif
      if (jobj && jenv->IsSameObject(jobj, NULL) == JNI_FALSE) {
        jmethodID disconn_meth = jenv->GetMethodID(jenv->GetObjectClass(jobj), disconn_method, "()V");
        if (disconn_meth) {
#if defined(DEBUG_DIRECTOR_OWNED)
          std::cout << "Swig::Director::disconnect_director_self upcall to " << disconn_method << std::endl;
#endif
          jenv->CallVoidMethod(jobj, disconn_meth);
        }
      }
      jenv->DeleteLocalRef(jobj);
    }

  public:
    Director(JNIEnv *jenv) : swig_jvm_((JavaVM *) NULL), swig_self_() {
      /* Acquire the Java VM pointer */
      jenv->GetJavaVM(&swig_jvm_);
    }

    virtual ~Director() {
      JNIEnvWrapper jnienv(this) ;
      JNIEnv *jenv = jnienv.getJNIEnv() ;
      swig_self_.release(jenv);
    }

    bool swig_set_self(JNIEnv *jenv, jobject jself, bool mem_own, bool weak_global) {
      return swig_self_.set(jenv, jself, mem_own, weak_global);
    }

    jobject swig_get_self(JNIEnv *jenv) const {
      return swig_self_.get(jenv);
    }

    // Change C++ object's ownership, relative to Java
    void swig_java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
      swig_self_.java_change_ownership(jenv, jself, take_or_release);
    }
  };


  // Utility methods and classes for exception handling.

  // Helper method to determine if a Java throwable matches a particular Java class type
  bool ExceptionMatches(JNIEnv *jenv, jthrowable excp, const char *clzname) {
    jboolean matches = false;

    if (excp && jenv && clzname) {
      // Exceptions need to be cleared for correct behavior.
      // The caller of ExceptionMatches should restore pending exceptions if desired - 
      // the caller already has the throwable.
      jenv->ExceptionClear();

      jclass clz = jenv->FindClass(clzname);
      if (clz && !jenv->ExceptionCheck()) {
	jclass classclz = jenv->GetObjectClass(clz);
	jmethodID isInstanceMethodID = jenv->GetMethodID(classclz, "isInstance", "(Ljava/lang/Object;)Z");
	if (isInstanceMethodID) {
	  matches = (jboolean)jenv->CallBooleanMethod(clz, isInstanceMethodID, excp);
	}
      }
      // This may happen if user typemaps or director:except
      // features call ExceptionMatches incorrectly, typically with
      // an invalid clzname argument.  Uncommenting the debug lines
      // may help to diagnose.
      // As is, this leaves the underlying case as a pending exception
      // which may not be that clear (e.g. ClassNotFoundException)

      // if (jenv->ExceptionCheck()) {
      // JavaExceptionMessage jstrmsg(jenv, jenv->ExceptionOccurred());
      // std::cerr << "Error: ExceptionMatches: class '" <<
      //   clzname << "' : " << jstrmsg.message() << std::endl;
      // }
    }
    return matches;
  }

  // Simple holder for a Java string during exception handling, allowing access to a c-style string
  class JavaString {
  public:
    JavaString(JNIEnv *jenv, jstring jstr) : jenv_(jenv), jstr_(jstr), cstr_(0) {
      if (jenv_ && jstr_)
	cstr_ = (const char *) jenv_->GetStringUTFChars(jstr_, NULL);
    }

    ~JavaString() {
      if (jenv_ && jstr_ && cstr_)
	jenv_->ReleaseStringUTFChars(jstr_, cstr_);
    }

    const char *cstr() {
      return cstr_ ? cstr_ : "";
    }

  private:
    // non-copyable
    JavaString(const JavaString &);
    JavaString &operator=(const JavaString &);

    JNIEnv *jenv_;
    jstring jstr_;
    const char *cstr_;
  };

  // Helper to extract the exception message from a Java throwable
  class JavaExceptionMessage {
  public:
    JavaExceptionMessage(JNIEnv *jenv, jthrowable excp) : jstrholder_(jenv, exceptionMsgJstr(jenv, excp)) {
    }

    ~JavaExceptionMessage() {
    }

    const char *message() {
      return jstrholder_.cstr();
    }

  private:
    // non-copyable
    JavaExceptionMessage(const JavaExceptionMessage &);
    JavaExceptionMessage &operator=(const JavaExceptionMessage &);

    // Static method to initialize jstrholder_
    static jstring exceptionMsgJstr(JNIEnv *jenv, jthrowable excp) {
      jstring jmsg = NULL;
      if (jenv && excp) {
	jenv->ExceptionClear(); // Cannot invoke methods with pending exception
	jclass thrwclz = jenv->GetObjectClass(excp);
	if (thrwclz) {
	  // if no getMessage() or other exception, no msg available.
	  jmethodID getThrowableMsgMethodID = jenv->GetMethodID(thrwclz, "getMessage", "()Ljava/lang/String;");
	  if (getThrowableMsgMethodID && !jenv->ExceptionCheck()) {
	    // if problem accessing exception message string, no msg will be available.
	    jmsg = (jstring)jenv->CallObjectMethod(excp, getThrowableMsgMethodID);
	  }
	}
	if (jmsg == NULL && jenv->ExceptionCheck()) {
	  jenv->ExceptionClear();
	}
      }
      return jmsg;
    }

    JavaString jstrholder_;
  };

  // C++ Exception class for converting from Java exceptions thrown during a director method Java upcall
  class DirectorException : public std::exception {
  public:

    // Construct a DirectorException from a Java throwable
    DirectorException(JNIEnv *jenv, jthrowable excp) : classname_(0), msg_(0) {
      jstring jstr_classname = NULL;

      if (excp) {
	// Get the exception class, like Exception
	jclass thrwclz = jenv->GetObjectClass(excp);
	if (thrwclz) {
	  // Get the Java.lang.Class class
	  jclass clzclz = jenv->GetObjectClass(thrwclz);
	  if (clzclz) {
	    jmethodID mid_getName = mid_getName = jenv->GetMethodID(clzclz, "getName", "()Ljava/lang/String;");
	    if (mid_getName) {
	      // Get the excp class name
	      jstr_classname = (jstring)(jenv->CallObjectMethod(thrwclz, mid_getName));
	    }
	  }
	}
      }
      // Copy strings, since no guarantee jenv will be active when handled
      // If classname_ is 0, returned as "UnknownException"
      if (jstr_classname) {
	JavaString classname(jenv, jstr_classname);
	classname_ = copypath(classname.cstr());
      }
      JavaExceptionMessage exceptionmsg(jenv, excp);
      msg_ = copystr(exceptionmsg.message());
    }

    // Throw as a wrapped RuntimeError explicitly.
    DirectorException(const char *msg) : classname_(0), msg_(0) {
      classname_ = copypath("java/lang/RuntimeError");
      msg_ = copystr(msg);
    }

    ~DirectorException() throw() {
      delete[] classname_;
      delete[] msg_;
    }

    // If there was a problem finding classname, keep track of error
    // On raiseJavaException will be mapped to a RuntimeException
    const char *classname() const throw() {
      return classname_ ? classname_ : "UnknownException";
    }

    const char *what() const throw() {
      return msg_ ? msg_ : "";
    }

    // Reconstruct and raise the Java Exception that caused the DirectorException
    void raiseJavaException(JNIEnv *jenv) {
      if (jenv) {
	jenv->ExceptionClear();

	jmethodID strCtorID = 0;
	jclass excpclz = jenv->FindClass(classname());

	if (excpclz) {
	  strCtorID = jenv->GetMethodID(excpclz,"<init>","(Ljava/lang/String;)V");
	}

	if (strCtorID) {
	  // If exception has a string constructor, throw an instance
	  jenv->ThrowNew(excpclz, what());
	} else {
	  // Else, throw a runtime
	  SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what() );
	}
      }
    }

  private:
    static const char *copypath(const char *srcmsg) {
      return copystr(srcmsg, 1);
    }

    static const char *copystr(const char *srcmsg, int pathrepl=0) {
      char *target = 0;
      if (srcmsg) {
	int msglen = 1 + strlen(srcmsg);
	target = new char[msglen];
	strncpy(target, srcmsg, msglen);
      }
      // If pathrepl, replace any '.' with '/'
      if (pathrepl) {
	for (char *c=target; *c; ++c) {
	  if ('.' == *c)
	    *c = '/';
	}
      }
      return target;
    }

    const char *classname_;
    const char *msg_;
  };

}

#endif /* __cplusplus */