Implement jfieldID and jmethodID properly.

Change-Id: I048107fbca4e21cf34e8fda6defdbc0b97421cf0
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index f28bff9..dbb2674 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -20,244 +20,123 @@
 
 namespace art {
 
-jobject NewObjectV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args);
-void CallNonvirtualVoidMethodV(JNIEnv* env, jobject obj, jclass clazz,
-                               jmethodID methodID, va_list args);
-void CallNonvirtualVoidMethodA(JNIEnv* env, jobject obj, jclass clazz,
-                               jmethodID methodID, jvalue* args);
+// This is private API, but with two different implementations: ARM and x86.
+void CreateInvokeStub(Assembler* assembler, Method* method);
 
-enum JNI_OnLoadState {
-  kPending = 0,     /* initial state, must be zero */
-  kFailed,
-  kOkay,
-};
+// TODO: this should be in our anonymous namespace, but is currently needed
+// for testing in "jni_internal_test.cc".
+bool EnsureInvokeStub(Method* method) {
+  if (method->GetInvokeStub() != NULL) {
+    return true;
+  }
+  // TODO: use signature to find a matching stub
+  // TODO: failed, acquire a lock on the stub table
+  Assembler assembler;
+  CreateInvokeStub(&assembler, method);
+  // TODO: store native_entry in the stub table
+  int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
+  size_t length = assembler.CodeSize();
+  void* addr = mmap(NULL, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (addr == MAP_FAILED) {
+    PLOG(FATAL) << "mmap failed";
+  }
+  MemoryRegion region(addr, length);
+  assembler.FinalizeInstructions(region);
+  method->SetInvokeStub(reinterpret_cast<Method::InvokeStub*>(region.pointer()));
+  return true;
+}
 
-struct SharedLibrary {
-  SharedLibrary() : jni_on_load_lock(Mutex::Create("JNI_OnLoad lock")) {
+// TODO: this can't be in our anonymous namespace because of the map in JavaVM.
+class SharedLibrary {
+public:
+  SharedLibrary(const std::string& path, void* handle, Object* class_loader)
+      : path_(path),
+        handle_(handle),
+        jni_on_load_lock_(Mutex::Create("JNI_OnLoad lock")),
+        jni_on_load_tid_(Thread::Current()->GetId()),
+        jni_on_load_result_(kPending) {
   }
 
   ~SharedLibrary() {
-    delete jni_on_load_lock;
+    delete jni_on_load_lock_;
   }
 
-  // Path to library "/system/lib/libjni.so".
-  std::string path;
-
-  // The void* returned by dlopen(3).
-  void* handle;
-
-  // The ClassLoader this library is associated with.
-  Object* class_loader;
-
-  // Guards remaining items.
-  Mutex* jni_on_load_lock;
-  // Wait for JNI_OnLoad in other thread.
-  pthread_cond_t  jni_on_load_cond;
-  // Recursive invocation guard.
-  uint32_t jni_on_load_tid;
-  // Result of earlier JNI_OnLoad call.
-  JNI_OnLoadState jni_on_load_result;
-};
-
-/*
- * Check the result of an earlier call to JNI_OnLoad on this library.  If
- * the call has not yet finished in another thread, wait for it.
- */
-bool CheckOnLoadResult(JavaVMExt* vm, SharedLibrary* library) {
-  Thread* self = Thread::Current();
-  if (library->jni_on_load_tid == self->GetId()) {
-    // Check this so we don't end up waiting for ourselves.  We need
-    // to return "true" so the caller can continue.
-    LOG(INFO) << *self << " recursive attempt to load library "
-              << "\"" << library->path << "\"";
-    return true;
+  Object* GetClassLoader() {
+    return class_loader_;
   }
 
-  UNIMPLEMENTED(ERROR) << "need to pthread_cond_wait!";
-  // MutexLock mu(library->jni_on_load_lock);
-  while (library->jni_on_load_result == kPending) {
-    if (vm->verbose_jni) {
-      LOG(INFO) << "[" << *self << " waiting for \"" << library->path << "\" "
-                << "JNI_OnLoad...]";
-    }
-    Thread::State old_state = self->GetState();
-    self->SetState(Thread::kWaiting); // TODO: VMWAIT
-    // pthread_cond_wait(&library->jni_on_load_cond, &library->jni_on_load_lock);
-    self->SetState(old_state);
-  }
-
-  bool okay = (library->jni_on_load_result == kOkay);
-  if (vm->verbose_jni) {
-    LOG(INFO) << "[Earlier JNI_OnLoad for \"" << library->path << "\" "
-              << (okay ? "succeeded" : "failed") << "]";
-  }
-  return okay;
-}
-
-typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
-
-/*
- * Load native code from the specified absolute pathname.  Per the spec,
- * if we've already loaded a library with the specified pathname, we
- * return without doing anything.
- *
- * TODO? for better results we should absolutify the pathname.  For fully
- * correct results we should stat to get the inode and compare that.  The
- * existing implementation is fine so long as everybody is using
- * System.loadLibrary.
- *
- * The library will be associated with the specified class loader.  The JNI
- * spec says we can't load the same library into more than one class loader.
- *
- * Returns "true" on success. On failure, sets *detail to a
- * human-readable description of the error or NULL if no detail is
- * available; ownership of the string is transferred to the caller.
- */
-bool JavaVMExt::LoadNativeLibrary(const std::string& path, Object* class_loader, char** detail) {
-  *detail = NULL;
-
-  // See if we've already loaded this library.  If we have, and the class loader
-  // matches, return successfully without doing anything.
-  SharedLibrary* library = libraries[path];
-  if (library != NULL) {
-    if (library->class_loader != class_loader) {
-      LOG(WARNING) << "Shared library \"" << path << "\" already opened by "
-                   << "ClassLoader " << library->class_loader << "; "
-                   << "can't open in " << class_loader;
-      *detail = strdup("already opened by different ClassLoader");
-      return false;
-    }
-    if (verbose_jni) {
-      LOG(INFO) << "[Shared library \"" << path << "\" already loaded in "
-                << "ClassLoader " << class_loader << "]";
-    }
-    if (!CheckOnLoadResult(this, library)) {
-      *detail = strdup("JNI_OnLoad failed before");
-      return false;
-    }
-    return true;
-  }
-
-  // Open the shared library.  Because we're using a full path, the system
-  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
-  // resolve this library's dependencies though.)
-
-  // Failures here are expected when java.library.path has several entries
-  // and we have to hunt for the lib.
-
-  // The current version of the dynamic linker prints detailed information
-  // about dlopen() failures.  Some things to check if the message is
-  // cryptic:
-  //   - make sure the library exists on the device
-  //   - verify that the right path is being opened (the debug log message
-  //     above can help with that)
-  //   - check to see if the library is valid (e.g. not zero bytes long)
-  //   - check config/prelink-linux-arm.map to ensure that the library
-  //     is listed and is not being overrun by the previous entry (if
-  //     loading suddenly stops working on a prelinked library, this is
-  //     a good one to check)
-  //   - write a trivial app that calls sleep() then dlopen(), attach
-  //     to it with "strace -p <pid>" while it sleeps, and watch for
-  //     attempts to open nonexistent dependent shared libs
-
-  // TODO: automate some of these checks!
-
-  // This can execute slowly for a large library on a busy system, so we
-  // want to switch from RUNNING to VMWAIT while it executes.  This allows
-  // the GC to ignore us.
-  Thread* self = Thread::Current();
-  Thread::State old_state = self->GetState();
-  self->SetState(Thread::kWaiting); // TODO: VMWAIT
-  void* handle = dlopen(path.c_str(), RTLD_LAZY);
-  self->SetState(old_state);
-
-  if (verbose_jni) {
-    LOG(INFO) << "[Call to dlopen(\"" << path << "\") returned " << handle << "]";
-  }
-
-  if (handle == NULL) {
-    *detail = strdup(dlerror());
-    return false;
-  }
-
-  // Create a new entry.
-  library = new SharedLibrary;
-  library->path = path;
-  library->handle = handle;
-  library->class_loader = class_loader;
-  UNIMPLEMENTED(ERROR) << "missing pthread_cond_init";
-  // pthread_cond_init(&library->onLoadCond, NULL);
-  library->jni_on_load_tid = self->GetId();
-
-  libraries[path] = library;
-
-//  if (pNewEntry != pActualEntry) {
-//    LOG(INFO) << "WOW: we lost a race to add a shared library (\"" << path << "\" ClassLoader=" << class_loader <<")";
-//    freeSharedLibEntry(pNewEntry);
-//    return CheckOnLoadResult(this, pActualEntry);
-//  } else
-  {
-    if (verbose_jni) {
-      LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
+  /*
+   * Check the result of an earlier call to JNI_OnLoad on this library.  If
+   * the call has not yet finished in another thread, wait for it.
+   */
+  bool CheckOnLoadResult(JavaVMExt* vm) {
+    Thread* self = Thread::Current();
+    if (jni_on_load_tid_ == self->GetId()) {
+      // Check this so we don't end up waiting for ourselves.  We need
+      // to return "true" so the caller can continue.
+      LOG(INFO) << *self << " recursive attempt to load library "
+                << "\"" << path_ << "\"";
+      return true;
     }
 
-    bool result = true;
-    void* sym = dlsym(handle, "JNI_OnLoad");
-    if (sym == NULL) {
-      if (verbose_jni) {
-        LOG(INFO) << "[No JNI_OnLoad found in \"" << path << "\"]";
+    UNIMPLEMENTED(ERROR) << "need to pthread_cond_wait!";
+    // MutexLock mu(jni_on_load_lock_);
+    while (jni_on_load_result_ == kPending) {
+      if (vm->verbose_jni) {
+        LOG(INFO) << "[" << *self << " waiting for \"" << path_ << "\" "
+                  << "JNI_OnLoad...]";
       }
-    } else {
-      // Call JNI_OnLoad.  We have to override the current class
-      // loader, which will always be "null" since the stuff at the
-      // top of the stack is around Runtime.loadLibrary().  (See
-      // the comments in the JNI FindClass function.)
-      UNIMPLEMENTED(WARNING) << "need to override current class loader";
-      JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
-      //Object* prevOverride = self->classLoaderOverride;
-      //self->classLoaderOverride = classLoader;
-
-      old_state = self->GetState();
-      self->SetState(Thread::kNative);
-      if (verbose_jni) {
-        LOG(INFO) << "[Calling JNI_OnLoad in \"" << path << "\"]";
-      }
-      int version = (*jni_on_load)(reinterpret_cast<JavaVM*>(this), NULL);
+      Thread::State old_state = self->GetState();
+      self->SetState(Thread::kWaiting); // TODO: VMWAIT
+      // pthread_cond_wait(&jni_on_load_cond_, &jni_on_load_lock_);
       self->SetState(old_state);
-
-      UNIMPLEMENTED(WARNING) << "need to restore current class loader";
-      //self->classLoaderOverride = prevOverride;
-
-      if (version != JNI_VERSION_1_2 &&
-          version != JNI_VERSION_1_4 &&
-          version != JNI_VERSION_1_6) {
-        LOG(WARNING) << "JNI_OnLoad in \"" << path << "\" returned "
-                     << "bad version: " << version;
-        // It's unwise to call dlclose() here, but we can mark it
-        // as bad and ensure that future load attempts will fail.
-        // We don't know how far JNI_OnLoad got, so there could
-        // be some partially-initialized stuff accessible through
-        // newly-registered native method calls.  We could try to
-        // unregister them, but that doesn't seem worthwhile.
-        result = false;
-      } else {
-        if (verbose_jni) {
-          LOG(INFO) << "[Returned " << (result ? "successfully" : "failure")
-                    << " from JNI_OnLoad in \"" << path << "\"]";
-        }
-      }
     }
 
-    library->jni_on_load_result = result ? kOkay : kFailed;
-    library->jni_on_load_tid = 0;
+    bool okay = (jni_on_load_result_ == kOkay);
+    if (vm->verbose_jni) {
+      LOG(INFO) << "[Earlier JNI_OnLoad for \"" << path_ << "\" "
+                << (okay ? "succeeded" : "failed") << "]";
+    }
+    return okay;
+  }
+
+  void SetResult(bool result) {
+    jni_on_load_result_ = result ? kOkay : kFailed;
+    jni_on_load_tid_ = 0;
 
     // Broadcast a wakeup to anybody sleeping on the condition variable.
     UNIMPLEMENTED(ERROR) << "missing pthread_cond_broadcast";
-    // MutexLock mu(library->jni_on_load_lock);
-    // pthread_cond_broadcast(&library->jni_on_load_cond);
-    return result;
+    // MutexLock mu(library->jni_on_load_lock_);
+    // pthread_cond_broadcast(&library->jni_on_load_cond_);
   }
-}
+
+ private:
+  enum JNI_OnLoadState {
+    kPending,
+    kFailed,
+    kOkay,
+  };
+
+  // Path to library "/system/lib/libjni.so".
+  std::string path_;
+
+  // The void* returned by dlopen(3).
+  void* handle_;
+
+  // The ClassLoader this library is associated with.
+  Object* class_loader_;
+
+  // Guards remaining items.
+  Mutex* jni_on_load_lock_;
+  // Wait for JNI_OnLoad in other thread.
+  pthread_cond_t jni_on_load_cond_;
+  // Recursive invocation guard.
+  uint32_t jni_on_load_tid_;
+  // Result of earlier JNI_OnLoad call.
+  JNI_OnLoadState jni_on_load_result_;
+};
+
+namespace {
 
 // Entry/exit processing for all JNI calls.
 //
@@ -355,6 +234,17 @@
   return reinterpret_cast<T>(ref);
 }
 
+jweak AddWeakGlobalReference(ScopedJniThreadState& ts, Object* obj) {
+  if (obj == NULL) {
+    return NULL;
+  }
+  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+  IndirectReferenceTable& weak_globals = vm->weak_globals;
+  MutexLock mu(vm->weak_globals_lock);
+  IndirectRef ref = weak_globals.Add(IRT_FIRST_SEGMENT, obj);
+  return reinterpret_cast<jweak>(ref);
+}
+
 template<typename T>
 T Decode(ScopedJniThreadState& ts, jobject obj) {
   if (obj == NULL) {
@@ -408,27 +298,12 @@
   return reinterpret_cast<T>(result);
 }
 
-void CreateInvokeStub(Assembler* assembler, Method* method);
+Field* DecodeField(ScopedJniThreadState& ts, jfieldID fid) {
+  return Decode<Field*>(ts, reinterpret_cast<jweak>(fid));
+}
 
-bool EnsureInvokeStub(Method* method) {
-  if (method->GetInvokeStub() != NULL) {
-    return true;
-  }
-  // TODO: use signature to find a matching stub
-  // TODO: failed, acquire a lock on the stub table
-  Assembler assembler;
-  CreateInvokeStub(&assembler, method);
-  // TODO: store native_entry in the stub table
-  int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
-  size_t length = assembler.CodeSize();
-  void* addr = mmap(NULL, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-  if (addr == MAP_FAILED) {
-    PLOG(FATAL) << "mmap failed";
-  }
-  MemoryRegion region(addr, length);
-  assembler.FinalizeInstructions(region);
-  method->SetInvokeStub(reinterpret_cast<Method::InvokeStub*>(region.pointer()));
-  return true;
+Method* DecodeMethod(ScopedJniThreadState& ts, jmethodID mid) {
+  return Decode<Method*>(ts, reinterpret_cast<jweak>(mid));
 }
 
 byte* CreateArgArray(ScopedJniThreadState& ts, Method* method, va_list ap) {
@@ -506,9 +381,8 @@
 }
 
 JValue InvokeWithArgArray(ScopedJniThreadState& ts, jobject obj,
-                          jmethodID method_id, byte* args) {
-  // TODO: DecodeReference
-  Method* method = reinterpret_cast<Method*>(method_id);
+                          jmethodID mid, byte* args) {
+  Method* method = DecodeMethod(ts, mid);
   Object* rcvr = Decode<Object*>(ts, obj);
   Thread* self = ts.Self();
 
@@ -536,28 +410,17 @@
 }
 
 JValue InvokeWithJValues(ScopedJniThreadState& ts, jobject obj,
-                         jmethodID method_id, jvalue* args) {
-  Method* method = reinterpret_cast<Method*>(method_id);
+                         jmethodID mid, jvalue* args) {
+  Method* method = DecodeMethod(ts, mid);
   scoped_array<byte> arg_array(CreateArgArray(ts, method, args));
-  return InvokeWithArgArray(ts, obj, method_id, arg_array.get());
+  return InvokeWithArgArray(ts, obj, mid, arg_array.get());
 }
 
 JValue InvokeWithVarArgs(ScopedJniThreadState& ts, jobject obj,
-                         jmethodID method_id, va_list args) {
-  Method* method = reinterpret_cast<Method*>(method_id);
+                         jmethodID mid, va_list args) {
+  Method* method = DecodeMethod(ts, mid);
   scoped_array<byte> arg_array(CreateArgArray(ts, method, args));
-  return InvokeWithArgArray(ts, obj, method_id, arg_array.get());
-}
-
-jint GetVersion(JNIEnv* env) {
-  ScopedJniThreadState ts(env);
-  return JNI_VERSION_1_6;
-}
-
-jclass DefineClass(JNIEnv* env, const char*, jobject, const jbyte*, jsize) {
-  ScopedJniThreadState ts(env);
-  LOG(WARNING) << "JNI DefineClass is not supported";
-  return NULL;
+  return InvokeWithArgArray(ts, obj, mid, arg_array.get());
 }
 
 // Section 12.3.2 of the JNI spec describes JNI class descriptors. They're
@@ -584,1268 +447,68 @@
   return result;
 }
 
-jclass FindClass(JNIEnv* env, const char* name) {
-  ScopedJniThreadState ts(env);
-  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  std::string descriptor(NormalizeJniClassDescriptor(name));
-  // TODO: need to get the appropriate ClassLoader.
-  Class* c = class_linker->FindClass(descriptor, NULL);
-  return AddLocalReference<jclass>(ts, c);
-}
-
-jmethodID FromReflectedMethod(JNIEnv* env, jobject method) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jfieldID FromReflectedField(JNIEnv* env, jobject field) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jobject ToReflectedMethod(JNIEnv* env, jclass cls,
-    jmethodID methodID, jboolean isStatic) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jclass GetSuperclass(JNIEnv* env, jclass sub) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jboolean IsAssignableFrom(JNIEnv* env, jclass sub, jclass sup) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
-}
-
-jobject ToReflectedField(JNIEnv* env, jclass cls,
-    jfieldID fieldID, jboolean isStatic) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jint Throw(JNIEnv* env, jthrowable obj) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint ThrowNew(JNIEnv* env, jclass clazz, const char* msg) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jthrowable ExceptionOccurred(JNIEnv* env) {
-  ScopedJniThreadState ts(env);
-  Object* exception = ts.Self()->GetException();
-  if (exception == NULL) {
-    return NULL;
-  } else {
-    // TODO: if adding a local reference failing causes the VM to abort
-    // then the following check will never occur.
-    jthrowable localException = AddLocalReference<jthrowable>(ts, exception);
-    if (localException == NULL) {
-      // We were unable to add a new local reference, and threw a new
-      // exception.  We can't return "exception", because it's not a
-      // local reference.  So we have to return NULL, indicating that
-      // there was no exception, even though it's pretty much raining
-      // exceptions in here.
-      LOG(WARNING) << "JNI WARNING: addLocal/exception combo";
-    }
-    return localException;
-  }
-}
-
-void ExceptionDescribe(JNIEnv* env) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ExceptionClear(JNIEnv* env) {
-  ScopedJniThreadState ts(env);
-  ts.Self()->ClearException();
-}
-
-void FatalError(JNIEnv* env, const char* msg) {
-  ScopedJniThreadState ts(env);
-  LOG(FATAL) << "JNI FatalError called: " << msg;
-}
-
-jint PushLocalFrame(JNIEnv* env, jint cap) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(WARNING) << "ignoring PushLocalFrame(" << cap << ")";
-  return JNI_OK;
-}
-
-jobject PopLocalFrame(JNIEnv* env, jobject res) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(WARNING) << "ignoring PopLocalFrame " << res;
-  return res;
-}
-
-jobject NewGlobalRef(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  if (obj == NULL) {
-    return NULL;
-  }
-
-  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-  IndirectReferenceTable& globals = vm->globals;
-  MutexLock mu(vm->globals_lock);
-  IndirectRef ref = globals.Add(IRT_FIRST_SEGMENT, Decode<Object*>(ts, obj));
-  return reinterpret_cast<jobject>(ref);
-}
-
-void DeleteGlobalRef(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  if (obj == NULL) {
-    return;
-  }
-
-  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-  IndirectReferenceTable& globals = vm->globals;
-  MutexLock mu(vm->globals_lock);
-
-  if (!globals.Remove(IRT_FIRST_SEGMENT, obj)) {
-    LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") "
-                 << "failed to find entry";
-  }
-}
-
-jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  if (obj == NULL) {
-    return NULL;
-  }
-
-  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-  IndirectReferenceTable& weak_globals = vm->weak_globals;
-  MutexLock mu(vm->weak_globals_lock);
-  IndirectRef ref = weak_globals.Add(IRT_FIRST_SEGMENT, Decode<Object*>(ts, obj));
-  return reinterpret_cast<jobject>(ref);
-}
-
-void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
-  ScopedJniThreadState ts(env);
-  if (obj == NULL) {
-    return;
-  }
-
-  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-  IndirectReferenceTable& weak_globals = vm->weak_globals;
-  MutexLock mu(vm->weak_globals_lock);
-
-  if (!weak_globals.Remove(IRT_FIRST_SEGMENT, obj)) {
-    LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "
-                 << "failed to find entry";
-  }
-}
-
-jobject NewLocalRef(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  if (obj == NULL) {
-    return NULL;
-  }
-
-  IndirectReferenceTable& locals = ts.Env()->locals;
-
-  uint32_t cookie = IRT_FIRST_SEGMENT; // TODO
-  IndirectRef ref = locals.Add(cookie, Decode<Object*>(ts, obj));
-  return reinterpret_cast<jobject>(ref);
-}
-
-void DeleteLocalRef(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  if (obj == NULL) {
-    return;
-  }
-
-  IndirectReferenceTable& locals = ts.Env()->locals;
-
-  uint32_t cookie = IRT_FIRST_SEGMENT; // TODO
-  if (!locals.Remove(cookie, obj)) {
-    // Attempting to delete a local reference that is not in the
-    // topmost local reference frame is a no-op.  DeleteLocalRef returns
-    // void and doesn't throw any exceptions, but we should probably
-    // complain about it so the user will notice that things aren't
-    // going quite the way they expect.
-    LOG(WARNING) << "JNI WARNING: DeleteLocalRef(" << obj << ") "
-                 << "failed to find entry";
-  }
-}
-
-jboolean IsSameObject(JNIEnv* env, jobject obj1, jobject obj2) {
-  ScopedJniThreadState ts(env);
-  return (Decode<Object*>(ts, obj1) == Decode<Object*>(ts, obj2))
-         ? JNI_TRUE : JNI_FALSE;
-}
-
-jint EnsureLocalCapacity(JNIEnv* env, jint) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jobject AllocObject(JNIEnv* env, jclass clazz) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jobject NewObject(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list args;
-  va_start(args, methodID);
-  jobject result = NewObjectV(env, clazz, methodID, args);
-  va_end(args);
-  return result;
-}
-
-jobject NewObjectV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  Class* klass = Decode<Class*>(ts, clazz);
-  Object* result = klass->NewInstance();
-  jobject local_result = AddLocalReference<jobject>(ts, result);
-  CallNonvirtualVoidMethodV(env, local_result, clazz, methodID, args);
-  return local_result;
-}
-
-jobject NewObjectA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  Class* klass = Decode<Class*>(ts, clazz);
-  Object* result = klass->NewInstance();
-  jobject local_result = AddLocalReference<jobjectArray>(ts, result);
-  CallNonvirtualVoidMethodA(env, local_result, clazz, methodID, args);
-  return local_result;
-}
-
-jclass GetObjectClass(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jboolean IsInstanceOf(JNIEnv* env, jobject jobj, jclass clazz) {
-  ScopedJniThreadState ts(env);
-  CHECK_NE(static_cast<jclass>(NULL), clazz);
-  if (jobj == NULL) {
-    // NB. JNI is different from regular Java instanceof in this respect
-    return JNI_TRUE;
-  } else {
-    Object* obj = Decode<Object*>(ts, jobj);
-    Class* klass = Decode<Class*>(ts, clazz);
-    return Object::InstanceOf(obj, klass) ? JNI_TRUE : JNI_FALSE;
-  }
-}
-
-jmethodID GetMethodID(JNIEnv* env,
-    jclass clazz, const char* name, const char* sig) {
-  ScopedJniThreadState ts(env);
-  Class* klass = Decode<Class*>(ts, clazz);
-  if (!klass->IsInitialized()) {
+jmethodID FindMethodID(ScopedJniThreadState& ts, jclass jni_class, const char* name, const char* sig, bool is_static) {
+  Class* c = Decode<Class*>(ts, jni_class);
+  if (!c->IsInitialized()) {
     // TODO: initialize the class
   }
-  Method* method = klass->FindVirtualMethod(name, sig);
-  if (method == NULL) {
-    // No virtual method matching the signature.  Search declared
-    // private methods and constructors.
-    method = klass->FindDeclaredDirectMethod(name, sig);
-  }
-  if (method == NULL) {
-    Thread* self = Thread::Current();
-    std::string class_name = klass->GetDescriptor().ToString();
-    // TODO: pretty print method names through a single routine
-    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
-                            "no method \"%s.%s%s\"",
-                            class_name.c_str(), name, sig);
-    return NULL;
-  } else if (method->IsStatic()) {
-    Thread* self = Thread::Current();
-    std::string class_name = klass->GetDescriptor().ToString();
-    // TODO: pretty print method names through a single routine
-    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
-                            "method \"%s.%s%s\" is static",
-                            class_name.c_str(), name, sig);
-    return NULL;
+
+  Method* method = NULL;
+  if (is_static) {
+    method = c->FindDirectMethod(name, sig);
   } else {
-    // TODO: create a JNI weak global reference for method
-    bool success = EnsureInvokeStub(method);
-    if (!success) {
-      // TODO: throw OutOfMemoryException
-      return NULL;
+    method = c->FindVirtualMethod(name, sig);
+    if (method == NULL) {
+      // No virtual method matching the signature.  Search declared
+      // private methods and constructors.
+      method = c->FindDeclaredDirectMethod(name, sig);
     }
-    return reinterpret_cast<jmethodID>(method);
   }
-}
 
-jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
+  if (method == NULL || method->IsStatic() != is_static) {
+    Thread* self = Thread::Current();
+    std::string class_name(c->GetDescriptor().ToString());
+    // TODO: try searching for the opposite kind of method from is_static
+    // for better diagnostics?
+    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+        "no %s method \"%s.%s%s\"", is_static ? "static" : "non-static",
+        class_name.c_str(), name, sig);
+    return NULL;
+  }
 
-jobject CallObjectMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
+  bool success = EnsureInvokeStub(method);
+  if (!success) {
+    // TODO: throw OutOfMemoryException
+    return NULL;
+  }
 
-jobject CallObjectMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue*  args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
+  return reinterpret_cast<jmethodID>(AddWeakGlobalReference(ts, method));
 }
 
-jboolean CallBooleanMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
-}
-
-jboolean CallBooleanMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
-}
-
-jboolean CallBooleanMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue*  args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
-}
-
-jbyte CallByteMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jbyte CallByteMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jbyte CallByteMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jchar CallCharMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jchar CallCharMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jchar CallCharMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jshort CallShortMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jshort CallShortMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jshort CallShortMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint CallIntMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint CallIntMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint CallIntMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jlong CallLongMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jlong CallLongMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jlong CallLongMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jfloat CallFloatMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jfloat CallFloatMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jfloat CallFloatMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jdouble CallDoubleMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jdouble CallDoubleMethodV(JNIEnv* env,
-    jobject obj, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jdouble CallDoubleMethodA(JNIEnv* env,
-    jobject obj, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void CallVoidMethodV(JNIEnv* env, jobject obj,
-    jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void CallVoidMethodA(JNIEnv* env, jobject obj,
-    jmethodID methodID, jvalue*  args) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-jobject CallNonvirtualObjectMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  jobject local_result = AddLocalReference<jobject>(ts, result.l);
-  va_end(ap);
-  return local_result;
-}
-
-jobject CallNonvirtualObjectMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, args);
-  return AddLocalReference<jobject>(ts, result.l);
-}
-
-jobject CallNonvirtualObjectMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue*  args) {
-  ScopedJniThreadState ts(env);
-  JValue result = InvokeWithJValues(ts, obj, methodID, args);
-  return AddLocalReference<jobject>(ts, result.l);
-}
-
-jboolean CallNonvirtualBooleanMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.z;
-}
-
-jboolean CallNonvirtualBooleanMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).z;
-}
-
-jboolean CallNonvirtualBooleanMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue*  args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).z;
-}
-
-jbyte CallNonvirtualByteMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.b;
-}
-
-jbyte CallNonvirtualByteMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).b;
-}
-
-jbyte CallNonvirtualByteMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).b;
-}
-
-jchar CallNonvirtualCharMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.c;
-}
-
-jchar CallNonvirtualCharMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).c;
-}
-
-jchar CallNonvirtualCharMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).c;
-}
-
-jshort CallNonvirtualShortMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.s;
-}
-
-jshort CallNonvirtualShortMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).s;
-}
-
-jshort CallNonvirtualShortMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).s;
-}
-
-jint CallNonvirtualIntMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.i;
-}
-
-jint CallNonvirtualIntMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).i;
-}
-
-jint CallNonvirtualIntMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).i;
-}
-
-jlong CallNonvirtualLongMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.j;
-}
-
-jlong CallNonvirtualLongMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).j;
-}
-
-jlong CallNonvirtualLongMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).j;
-}
-
-jfloat CallNonvirtualFloatMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.f;
-}
-
-jfloat CallNonvirtualFloatMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).f;
-}
-
-jfloat CallNonvirtualFloatMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).f;
-}
-
-jdouble CallNonvirtualDoubleMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-  return result.d;
-}
-
-jdouble CallNonvirtualDoubleMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, obj, methodID, args).d;
-}
-
-jdouble CallNonvirtualDoubleMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, obj, methodID, args).d;
-}
-
-void CallNonvirtualVoidMethod(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  InvokeWithVarArgs(ts, obj, methodID, ap);
-  va_end(ap);
-}
-
-void CallNonvirtualVoidMethodV(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  InvokeWithVarArgs(ts, obj, methodID, args);
-}
-
-void CallNonvirtualVoidMethodA(JNIEnv* env,
-    jobject obj, jclass clazz, jmethodID methodID, jvalue*  args) {
-  ScopedJniThreadState ts(env);
-  InvokeWithJValues(ts, obj, methodID, args);
-}
-
-jfieldID GetFieldID(JNIEnv* env,
-    jclass clazz, const char* name, const char* sig) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jobject GetObjectField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jboolean GetBooleanField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
-}
-
-jbyte GetByteField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jchar GetCharField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jshort GetShortField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint GetIntField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jlong GetLongField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jfloat GetFloatField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jdouble GetDoubleField(JNIEnv* env, jobject obj, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-void SetObjectField(JNIEnv* env, jobject obj, jfieldID fieldID, jobject val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetBooleanField(JNIEnv* env, jobject obj, jfieldID fieldID, jboolean val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetByteField(JNIEnv* env, jobject obj, jfieldID fieldID, jbyte val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetCharField(JNIEnv* env, jobject obj, jfieldID fieldID, jchar val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetShortField(JNIEnv* env, jobject obj, jfieldID fieldID, jshort val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetIntField(JNIEnv* env, jobject obj, jfieldID fieldID, jint val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetLongField(JNIEnv* env, jobject obj, jfieldID fieldID, jlong val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetFloatField(JNIEnv* env, jobject obj, jfieldID fieldID, jfloat val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetDoubleField(JNIEnv* env, jobject obj, jfieldID fieldID, jdouble val) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-jmethodID GetStaticMethodID(JNIEnv* env,
-    jclass clazz, const char* name, const char* sig) {
-  ScopedJniThreadState ts(env);
-  Class* klass = Decode<Class*>(ts, clazz);
-  if (!klass->IsInitialized()) {
+jfieldID FindFieldID(ScopedJniThreadState& ts, jclass jni_class, const char* name, const char* sig, bool is_static) {
+  Class* c = Decode<Class*>(ts, jni_class);
+  if (!c->IsInitialized()) {
     // TODO: initialize the class
   }
-  Method* method = klass->FindDirectMethod(name, sig);
-  if (method == NULL) {
-    Thread* self = Thread::Current();
-    std::string class_name = klass->GetDescriptor().ToString();
-    // TODO: pretty print method names through a single routine
-    // TODO: may want to FindVirtualMethod to give more informative error
-    // message here
-    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
-                            "no method \"%s.%s%s\"",
-                            class_name.c_str(), name, sig);
-    return NULL;
-  } else if (!method->IsStatic()) {
-    Thread* self = Thread::Current();
-    std::string class_name = klass->GetDescriptor().ToString();
-    // TODO: pretty print method names through a single routine
-    self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
-                            "method \"%s.%s%s\" is not static",
-                            class_name.c_str(), name, sig);
-    return NULL;
+
+  Field* field = NULL;
+  if (is_static) {
+    field = c->FindStaticField(name, sig);
   } else {
-    // TODO: create a JNI weak global reference for method
-    bool success = EnsureInvokeStub(method);
-    if (!success) {
-      // TODO: throw OutOfMemoryException
-      return NULL;
-    }
-    return reinterpret_cast<jmethodID>(method);
+    field = c->FindInstanceField(name, sig);
   }
-}
 
-jobject CallStaticObjectMethod(JNIEnv* env,
-    jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  jobject local_result = AddLocalReference<jobject>(ts, result.l);
-  va_end(ap);
-  return local_result;
-}
-
-jobject CallStaticObjectMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, args);
-  return AddLocalReference<jobject>(ts, result.l);
-}
-
-jobject CallStaticObjectMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  JValue result = InvokeWithJValues(ts, NULL, methodID, args);
-  return AddLocalReference<jobject>(ts, result.l);
-}
-
-jboolean CallStaticBooleanMethod(JNIEnv* env,
-    jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.z;
-}
-
-jboolean CallStaticBooleanMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).z;
-}
-
-jboolean CallStaticBooleanMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).z;
-}
-
-jbyte CallStaticByteMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.b;
-}
-
-jbyte CallStaticByteMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).b;
-}
-
-jbyte CallStaticByteMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).b;
-}
-
-jchar CallStaticCharMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.c;
-}
-
-jchar CallStaticCharMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).c;
-}
-
-jchar CallStaticCharMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).c;
-}
-
-jshort CallStaticShortMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.s;
-}
-
-jshort CallStaticShortMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).s;
-}
-
-jshort CallStaticShortMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).s;
-}
-
-jint CallStaticIntMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.i;
-}
-
-jint CallStaticIntMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).i;
-}
-
-jint CallStaticIntMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).i;
-}
-
-jlong CallStaticLongMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.j;
-}
-
-jlong CallStaticLongMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).j;
-}
-
-jlong CallStaticLongMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).j;
-}
-
-jfloat CallStaticFloatMethod(JNIEnv* env, jclass cls, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.f;
-}
-
-jfloat CallStaticFloatMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).f;
-}
-
-jfloat CallStaticFloatMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).f;
-}
-
-jdouble CallStaticDoubleMethod(JNIEnv* env, jclass cls, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-  return result.d;
-}
-
-jdouble CallStaticDoubleMethodV(JNIEnv* env,
-    jclass clazz, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithVarArgs(ts, NULL, methodID, args).d;
-}
-
-jdouble CallStaticDoubleMethodA(JNIEnv* env,
-    jclass clazz, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  return InvokeWithJValues(ts, NULL, methodID, args).d;
-}
-
-void CallStaticVoidMethod(JNIEnv* env, jclass cls, jmethodID methodID, ...) {
-  ScopedJniThreadState ts(env);
-  va_list ap;
-  va_start(ap, methodID);
-  InvokeWithVarArgs(ts, NULL, methodID, ap);
-  va_end(ap);
-}
-
-void CallStaticVoidMethodV(JNIEnv* env,
-    jclass cls, jmethodID methodID, va_list args) {
-  ScopedJniThreadState ts(env);
-  InvokeWithVarArgs(ts, NULL, methodID, args);
-}
-
-void CallStaticVoidMethodA(JNIEnv* env,
-    jclass cls, jmethodID methodID, jvalue* args) {
-  ScopedJniThreadState ts(env);
-  InvokeWithJValues(ts, NULL, methodID, args);
-}
-
-jfieldID GetStaticFieldID(JNIEnv* env,
-    jclass clazz, const char* name, const char* sig) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jobject GetStaticObjectField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jboolean GetStaticBooleanField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNI_FALSE;
-}
-
-jbyte GetStaticByteField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jchar GetStaticCharField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jshort GetStaticShortField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint GetStaticIntField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jlong GetStaticLongField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jfloat GetStaticFloatField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jdouble GetStaticDoubleField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-void SetStaticObjectField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jobject value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticBooleanField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jboolean value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticByteField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jbyte value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticCharField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jchar value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticShortField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jshort value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticIntField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jint value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticLongField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jlong value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticFloatField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jfloat value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetStaticDoubleField(JNIEnv* env,
-    jclass clazz, jfieldID fieldID, jdouble value) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-jstring NewString(JNIEnv* env, const jchar* unicode, jsize len) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jsize GetStringLength(JNIEnv* env, jstring str) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-const jchar* GetStringChars(JNIEnv* env, jstring str, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-void ReleaseStringChars(JNIEnv* env, jstring str, const jchar* chars) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-jstring NewStringUTF(JNIEnv* env, const char* utf) {
-  ScopedJniThreadState ts(env);
-  if (utf == NULL) {
+  if (field == NULL) {
+    Thread* self = Thread::Current();
+    std::string class_name(c->GetDescriptor().ToString());
+    self->ThrowNewException("Ljava/lang/NoSuchFieldError;",
+        "no \"%s\" field \"%s\" in class \"%s\" or its superclasses", sig,
+        name, class_name.c_str());
     return NULL;
   }
-  String* result = String::AllocFromModifiedUtf8(utf);
-  return AddLocalReference<jstring>(ts, result);
-}
 
-jsize GetStringUTFLength(JNIEnv* env, jstring str) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-const char* GetStringUTFChars(JNIEnv* env, jstring str, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-void ReleaseStringUTFChars(JNIEnv* env, jstring str, const char* chars) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-jsize GetArrayLength(JNIEnv* env, jarray array) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-void SetObjectArrayElement(JNIEnv* env,
-    jobjectArray java_array, jsize index, jobject java_value) {
-  ScopedJniThreadState ts(env);
-  ObjectArray<Object>* array = Decode<ObjectArray<Object>*>(ts, java_array);
-  Object* value = Decode<Object*>(ts, java_value);
-  array->Set(index, value);
+  jweak fid = AddWeakGlobalReference(ts, field);
+  return reinterpret_cast<jfieldID>(fid);
 }
 
 template<typename JniT, typename ArtT>
@@ -1855,633 +518,1835 @@
   return AddLocalReference<JniT>(ts, result);
 }
 
-jbooleanArray NewBooleanArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jbooleanArray, BooleanArray>(ts, length);
-}
+}  // namespace
 
-jbyteArray NewByteArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jbyteArray, ByteArray>(ts, length);
-}
+class JNI {
+ public:
 
-jcharArray NewCharArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jcharArray, CharArray>(ts, length);
-}
+  static jint GetVersion(JNIEnv* env) {
+    ScopedJniThreadState ts(env);
+    return JNI_VERSION_1_6;
+  }
 
-jdoubleArray NewDoubleArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jdoubleArray, DoubleArray>(ts, length);
-}
-
-jfloatArray NewFloatArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jfloatArray, FloatArray>(ts, length);
-}
-
-jintArray NewIntArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jintArray, IntArray>(ts, length);
-}
-
-jlongArray NewLongArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jlongArray, LongArray>(ts, length);
-}
-
-jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_jclass, jobject initial_element) {
-  ScopedJniThreadState ts(env);
-  CHECK_GE(length, 0); // TODO: ReportJniError
-
-  // Compute the array class corresponding to the given element class.
-  Class* element_class = Decode<Class*>(ts, element_jclass);
-  std::string descriptor;
-  descriptor += "[";
-  descriptor += element_class->GetDescriptor().ToString();
-
-  // Find the class.
-  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  // TODO: need to get the appropriate ClassLoader.
-  Class* array_class = class_linker->FindClass(descriptor, NULL);
-  if (array_class == NULL) {
+  static jclass DefineClass(JNIEnv* env, const char*, jobject, const jbyte*, jsize) {
+    ScopedJniThreadState ts(env);
+    LOG(WARNING) << "JNI DefineClass is not supported";
     return NULL;
   }
 
-  ObjectArray<Object>* result = ObjectArray<Object>::Alloc(array_class, length);
-  CHECK(initial_element == NULL);  // TODO: support initial_element
-  return AddLocalReference<jobjectArray>(ts, result);
-}
-
-jshortArray NewShortArray(JNIEnv* env, jsize length) {
-  ScopedJniThreadState ts(env);
-  return NewPrimitiveArray<jshortArray, ShortArray>(ts, length);
-}
-
-jboolean* GetBooleanArrayElements(JNIEnv* env,
-    jbooleanArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jbyte* GetByteArrayElements(JNIEnv* env, jbyteArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jchar* GetCharArrayElements(JNIEnv* env, jcharArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jshort* GetShortArrayElements(JNIEnv* env,
-    jshortArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jint* GetIntArrayElements(JNIEnv* env, jintArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jlong* GetLongArrayElements(JNIEnv* env, jlongArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jfloat* GetFloatArrayElements(JNIEnv* env,
-    jfloatArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-jdouble* GetDoubleArrayElements(JNIEnv* env,
-    jdoubleArray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
-
-void ReleaseBooleanArrayElements(JNIEnv* env,
-    jbooleanArray array, jboolean* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseByteArrayElements(JNIEnv* env,
-    jbyteArray array, jbyte* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseCharArrayElements(JNIEnv* env,
-    jcharArray array, jchar* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseShortArrayElements(JNIEnv* env,
-    jshortArray array, jshort* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseIntArrayElements(JNIEnv* env,
-    jintArray array, jint* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseLongArrayElements(JNIEnv* env,
-    jlongArray array, jlong* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseFloatArrayElements(JNIEnv* env,
-    jfloatArray array, jfloat* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void ReleaseDoubleArrayElements(JNIEnv* env,
-    jdoubleArray array, jdouble* elems, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetBooleanArrayRegion(JNIEnv* env,
-    jbooleanArray array, jsize start, jsize l, jboolean* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetByteArrayRegion(JNIEnv* env,
-    jbyteArray array, jsize start, jsize len, jbyte* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetCharArrayRegion(JNIEnv* env,
-    jcharArray array, jsize start, jsize len, jchar* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetShortArrayRegion(JNIEnv* env,
-    jshortArray array, jsize start, jsize len, jshort* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetIntArrayRegion(JNIEnv* env,
-    jintArray array, jsize start, jsize len, jint* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetLongArrayRegion(JNIEnv* env,
-    jlongArray array, jsize start, jsize len, jlong* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetFloatArrayRegion(JNIEnv* env,
-    jfloatArray array, jsize start, jsize len, jfloat* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void GetDoubleArrayRegion(JNIEnv* env,
-    jdoubleArray array, jsize start, jsize len, jdouble* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetBooleanArrayRegion(JNIEnv* env,
-    jbooleanArray array, jsize start, jsize l, const jboolean* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetByteArrayRegion(JNIEnv* env,
-    jbyteArray array, jsize start, jsize len, const jbyte* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetCharArrayRegion(JNIEnv* env,
-    jcharArray array, jsize start, jsize len, const jchar* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetShortArrayRegion(JNIEnv* env,
-    jshortArray array, jsize start, jsize len, const jshort* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetIntArrayRegion(JNIEnv* env,
-    jintArray array, jsize start, jsize len, const jint* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetLongArrayRegion(JNIEnv* env,
-    jlongArray array, jsize start, jsize len, const jlong* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetFloatArrayRegion(JNIEnv* env,
-    jfloatArray array, jsize start, jsize len, const jfloat* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-void SetDoubleArrayRegion(JNIEnv* env,
-    jdoubleArray array, jsize start, jsize len, const jdouble* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
-
-jint RegisterNatives(JNIEnv* env,
-    jclass clazz, const JNINativeMethod* methods, jint nMethods) {
-  ScopedJniThreadState ts(env);
-  Class* klass = Decode<Class*>(ts, clazz);
-  for(int i = 0; i < nMethods; i++) {
-    const char* name = methods[i].name;
-    const char* sig = methods[i].signature;
-
-    if (*sig == '!') {
-      // TODO: fast jni. it's too noisy to log all these.
-      ++sig;
-    }
-
-    Method* method = klass->FindDirectMethod(name, sig);
-    if (method == NULL) {
-      method = klass->FindVirtualMethod(name, sig);
-    }
-    if (method == NULL) {
-      Thread* self = Thread::Current();
-      std::string class_name = klass->GetDescriptor().ToString();
-      // TODO: pretty print method names through a single routine
-      self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
-                              "no method \"%s.%s%s\"",
-                              class_name.c_str(), name, sig);
-      return JNI_ERR;
-    } else if (!method->IsNative()) {
-      Thread* self = Thread::Current();
-      std::string class_name = klass->GetDescriptor().ToString();
-      // TODO: pretty print method names through a single routine
-      self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
-                              "method \"%s.%s%s\" is not native",
-                              class_name.c_str(), name, sig);
-      return JNI_ERR;
-    }
-    method->RegisterNative(methods[i].fnPtr);
+  static jclass FindClass(JNIEnv* env, const char* name) {
+    ScopedJniThreadState ts(env);
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    std::string descriptor(NormalizeJniClassDescriptor(name));
+    // TODO: need to get the appropriate ClassLoader.
+    Class* c = class_linker->FindClass(descriptor, NULL);
+    return AddLocalReference<jclass>(ts, c);
   }
-  return JNI_OK;
-}
 
-jint UnregisterNatives(JNIEnv* env, jclass clazz) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
-
-jint MonitorEnter(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(WARNING);
-  return 0;
-}
-
-jint MonitorExit(JNIEnv* env, jobject obj) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(WARNING);
-  return 0;
-}
-
-jint GetJavaVM(JNIEnv* env, JavaVM** vm) {
-  ScopedJniThreadState ts(env);
-  Runtime* runtime = Runtime::Current();
-  if (runtime != NULL) {
-    *vm = reinterpret_cast<JavaVM*>(runtime->GetJavaVM());
-  } else {
-    *vm = NULL;
+  static jmethodID FromReflectedMethod(JNIEnv* env, jobject java_method) {
+    ScopedJniThreadState ts(env);
+    Method* method = Decode<Method*>(ts, java_method);
+    return reinterpret_cast<jmethodID>(AddWeakGlobalReference(ts, method));
   }
-  return (*vm != NULL) ? JNI_OK : JNI_ERR;
-}
 
-void GetStringRegion(JNIEnv* env,
-    jstring str, jsize start, jsize len, jchar* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
+  static jfieldID FromReflectedField(JNIEnv* env, jobject java_field) {
+    ScopedJniThreadState ts(env);
+    Field* field = Decode<Field*>(ts, java_field);
+    return reinterpret_cast<jfieldID>(AddWeakGlobalReference(ts, field));
+  }
 
-void GetStringUTFRegion(JNIEnv* env,
-    jstring str, jsize start, jsize len, char* buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
+  static jobject ToReflectedMethod(JNIEnv* env, jclass, jmethodID mid, jboolean) {
+    ScopedJniThreadState ts(env);
+    Method* method = DecodeMethod(ts, mid);
+    return AddLocalReference<jobject>(ts, method);
+  }
 
-void* GetPrimitiveArrayCritical(JNIEnv* env,
-    jarray array, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
+  static jobject ToReflectedField(JNIEnv* env, jclass, jfieldID fid, jboolean) {
+    ScopedJniThreadState ts(env);
+    Field* field = DecodeField(ts, fid);
+    return AddLocalReference<jobject>(ts, field);
+  }
 
-void ReleasePrimitiveArrayCritical(JNIEnv* env,
-    jarray array, void* carray, jint mode) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
+  static jclass GetSuperclass(JNIEnv* env, jclass sub) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
 
-const jchar* GetStringCritical(JNIEnv* env, jstring s, jboolean* isCopy) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
+  static jboolean IsAssignableFrom(JNIEnv* env, jclass sub, jclass sup) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNI_FALSE;
+  }
 
-void ReleaseStringCritical(JNIEnv* env, jstring s, const jchar* cstr) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-}
+  static jint Throw(JNIEnv* env, jthrowable obj) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
 
-jboolean ExceptionCheck(JNIEnv* env) {
-  ScopedJniThreadState ts(env);
-  return ts.Self()->IsExceptionPending() ? JNI_TRUE : JNI_FALSE;
-}
+  static jint ThrowNew(JNIEnv* env, jclass clazz, const char* msg) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
 
-jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
+  static jthrowable ExceptionOccurred(JNIEnv* env) {
+    ScopedJniThreadState ts(env);
+    Object* exception = ts.Self()->GetException();
+    if (exception == NULL) {
+      return NULL;
+    } else {
+      // TODO: if adding a local reference failing causes the VM to abort
+      // then the following check will never occur.
+      jthrowable localException = AddLocalReference<jthrowable>(ts, exception);
+      if (localException == NULL) {
+        // We were unable to add a new local reference, and threw a new
+        // exception.  We can't return "exception", because it's not a
+        // local reference.  So we have to return NULL, indicating that
+        // there was no exception, even though it's pretty much raining
+        // exceptions in here.
+        LOG(WARNING) << "JNI WARNING: addLocal/exception combo";
+      }
+      return localException;
+    }
+  }
+
+  static void ExceptionDescribe(JNIEnv* env) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ExceptionClear(JNIEnv* env) {
+    ScopedJniThreadState ts(env);
+    ts.Self()->ClearException();
+  }
+
+  static void FatalError(JNIEnv* env, const char* msg) {
+    ScopedJniThreadState ts(env);
+    LOG(FATAL) << "JNI FatalError called: " << msg;
+  }
+
+  static jint PushLocalFrame(JNIEnv* env, jint cap) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(WARNING) << "ignoring PushLocalFrame(" << cap << ")";
+    return JNI_OK;
+  }
+
+  static jobject PopLocalFrame(JNIEnv* env, jobject res) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(WARNING) << "ignoring PopLocalFrame " << res;
+    return res;
+  }
+
+  static jobject NewGlobalRef(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    if (obj == NULL) {
+      return NULL;
+    }
+
+    JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+    IndirectReferenceTable& globals = vm->globals;
+    MutexLock mu(vm->globals_lock);
+    IndirectRef ref = globals.Add(IRT_FIRST_SEGMENT, Decode<Object*>(ts, obj));
+    return reinterpret_cast<jobject>(ref);
+  }
+
+  static void DeleteGlobalRef(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    if (obj == NULL) {
+      return;
+    }
+
+    JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+    IndirectReferenceTable& globals = vm->globals;
+    MutexLock mu(vm->globals_lock);
+
+    if (!globals.Remove(IRT_FIRST_SEGMENT, obj)) {
+      LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") "
+                   << "failed to find entry";
+    }
+  }
+
+  static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    return AddWeakGlobalReference(ts, Decode<Object*>(ts, obj));
+  }
+
+  static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
+    ScopedJniThreadState ts(env);
+    if (obj == NULL) {
+      return;
+    }
+
+    JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+    IndirectReferenceTable& weak_globals = vm->weak_globals;
+    MutexLock mu(vm->weak_globals_lock);
+
+    if (!weak_globals.Remove(IRT_FIRST_SEGMENT, obj)) {
+      LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "
+                   << "failed to find entry";
+    }
+  }
+
+  static jobject NewLocalRef(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    if (obj == NULL) {
+      return NULL;
+    }
+
+    IndirectReferenceTable& locals = ts.Env()->locals;
+
+    uint32_t cookie = IRT_FIRST_SEGMENT; // TODO
+    IndirectRef ref = locals.Add(cookie, Decode<Object*>(ts, obj));
+    return reinterpret_cast<jobject>(ref);
+  }
+
+  static void DeleteLocalRef(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    if (obj == NULL) {
+      return;
+    }
+
+    IndirectReferenceTable& locals = ts.Env()->locals;
+
+    uint32_t cookie = IRT_FIRST_SEGMENT; // TODO
+    if (!locals.Remove(cookie, obj)) {
+      // Attempting to delete a local reference that is not in the
+      // topmost local reference frame is a no-op.  DeleteLocalRef returns
+      // void and doesn't throw any exceptions, but we should probably
+      // complain about it so the user will notice that things aren't
+      // going quite the way they expect.
+      LOG(WARNING) << "JNI WARNING: DeleteLocalRef(" << obj << ") "
+                   << "failed to find entry";
+    }
+  }
+
+  static jboolean IsSameObject(JNIEnv* env, jobject obj1, jobject obj2) {
+    ScopedJniThreadState ts(env);
+    return (Decode<Object*>(ts, obj1) == Decode<Object*>(ts, obj2))
+        ? JNI_TRUE : JNI_FALSE;
+  }
+
+  static jint EnsureLocalCapacity(JNIEnv* env, jint) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jobject AllocObject(JNIEnv* env, jclass clazz) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jobject NewObject(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list args;
+    va_start(args, methodID);
+    jobject result = NewObjectV(env, clazz, methodID, args);
+    va_end(args);
+    return result;
+  }
+
+  static jobject NewObjectV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    Class* klass = Decode<Class*>(ts, clazz);
+    Object* result = klass->NewInstance();
+    jobject local_result = AddLocalReference<jobject>(ts, result);
+    CallNonvirtualVoidMethodV(env, local_result, clazz, methodID, args);
+    return local_result;
+  }
+
+  static jobject NewObjectA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    Class* klass = Decode<Class*>(ts, clazz);
+    Object* result = klass->NewInstance();
+    jobject local_result = AddLocalReference<jobjectArray>(ts, result);
+    CallNonvirtualVoidMethodA(env, local_result, clazz, methodID, args);
+    return local_result;
+  }
+
+  static jclass GetObjectClass(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jboolean IsInstanceOf(JNIEnv* env, jobject jobj, jclass clazz) {
+    ScopedJniThreadState ts(env);
+    CHECK_NE(static_cast<jclass>(NULL), clazz);
+    if (jobj == NULL) {
+      // NB. JNI is different from regular Java instanceof in this respect
+      return JNI_TRUE;
+    } else {
+      Object* obj = Decode<Object*>(ts, jobj);
+      Class* klass = Decode<Class*>(ts, clazz);
+      return Object::InstanceOf(obj, klass) ? JNI_TRUE : JNI_FALSE;
+    }
+  }
+
+  static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    ScopedJniThreadState ts(env);
+    return FindMethodID(ts, c, name, sig, false);
+  }
+
+  static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    ScopedJniThreadState ts(env);
+    return FindMethodID(ts, c, name, sig, true);
+  }
+
+  static jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jobject CallObjectMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jobject CallObjectMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue*  args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jboolean CallBooleanMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNI_FALSE;
+  }
+
+  static jboolean CallBooleanMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNI_FALSE;
+  }
+
+  static jboolean CallBooleanMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue*  args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNI_FALSE;
+  }
+
+  static jbyte CallByteMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jbyte CallByteMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jbyte CallByteMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jchar CallCharMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jchar CallCharMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jchar CallCharMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jshort CallShortMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jshort CallShortMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jshort CallShortMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jint CallIntMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jint CallIntMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jint CallIntMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jlong CallLongMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jlong CallLongMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jlong CallLongMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jfloat CallFloatMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jfloat CallFloatMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jfloat CallFloatMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jdouble CallDoubleMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jdouble CallDoubleMethodV(JNIEnv* env,
+      jobject obj, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jdouble CallDoubleMethodA(JNIEnv* env,
+      jobject obj, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void CallVoidMethodV(JNIEnv* env, jobject obj,
+      jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void CallVoidMethodA(JNIEnv* env, jobject obj,
+      jmethodID methodID, jvalue*  args) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jobject CallNonvirtualObjectMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    jobject local_result = AddLocalReference<jobject>(ts, result.l);
+    va_end(ap);
+    return local_result;
+  }
+
+  static jobject CallNonvirtualObjectMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, args);
+    return AddLocalReference<jobject>(ts, result.l);
+  }
+
+  static jobject CallNonvirtualObjectMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue*  args) {
+    ScopedJniThreadState ts(env);
+    JValue result = InvokeWithJValues(ts, obj, methodID, args);
+    return AddLocalReference<jobject>(ts, result.l);
+  }
+
+  static jboolean CallNonvirtualBooleanMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.z;
+  }
+
+  static jboolean CallNonvirtualBooleanMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).z;
+  }
+
+  static jboolean CallNonvirtualBooleanMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue*  args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).z;
+  }
+
+  static jbyte CallNonvirtualByteMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.b;
+  }
+
+  static jbyte CallNonvirtualByteMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).b;
+  }
+
+  static jbyte CallNonvirtualByteMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).b;
+  }
+
+  static jchar CallNonvirtualCharMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.c;
+  }
+
+  static jchar CallNonvirtualCharMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).c;
+  }
+
+  static jchar CallNonvirtualCharMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).c;
+  }
+
+  static jshort CallNonvirtualShortMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.s;
+  }
+
+  static jshort CallNonvirtualShortMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).s;
+  }
+
+  static jshort CallNonvirtualShortMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).s;
+  }
+
+  static jint CallNonvirtualIntMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.i;
+  }
+
+  static jint CallNonvirtualIntMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).i;
+  }
+
+  static jint CallNonvirtualIntMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).i;
+  }
+
+  static jlong CallNonvirtualLongMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.j;
+  }
+
+  static jlong CallNonvirtualLongMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).j;
+  }
+
+  static jlong CallNonvirtualLongMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).j;
+  }
+
+  static jfloat CallNonvirtualFloatMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.f;
+  }
+
+  static jfloat CallNonvirtualFloatMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).f;
+  }
+
+  static jfloat CallNonvirtualFloatMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).f;
+  }
+
+  static jdouble CallNonvirtualDoubleMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+    return result.d;
+  }
+
+  static jdouble CallNonvirtualDoubleMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, obj, methodID, args).d;
+  }
+
+  static jdouble CallNonvirtualDoubleMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, obj, methodID, args).d;
+  }
+
+  static void CallNonvirtualVoidMethod(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    InvokeWithVarArgs(ts, obj, methodID, ap);
+    va_end(ap);
+  }
+
+  static void CallNonvirtualVoidMethodV(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    InvokeWithVarArgs(ts, obj, methodID, args);
+  }
+
+  static void CallNonvirtualVoidMethodA(JNIEnv* env,
+      jobject obj, jclass clazz, jmethodID methodID, jvalue*  args) {
+    ScopedJniThreadState ts(env);
+    InvokeWithJValues(ts, obj, methodID, args);
+  }
+
+  static jfieldID GetFieldID(JNIEnv* env,
+      jclass c, const char* name, const char* sig) {
+    ScopedJniThreadState ts(env);
+    return FindFieldID(ts, c, name, sig, false);
+  }
 
 
-void* GetDirectBufferAddress(JNIEnv* env, jobject buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return NULL;
-}
+  static jfieldID GetStaticFieldID(JNIEnv* env,
+      jclass c, const char* name, const char* sig) {
+    ScopedJniThreadState ts(env);
+    return FindFieldID(ts, c, name, sig, true);
+  }
 
-jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return 0;
-}
+  static jobject GetObjectField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
 
-jobjectRefType GetObjectRefType(JNIEnv* env, jobject jobj) {
-  ScopedJniThreadState ts(env);
-  UNIMPLEMENTED(FATAL);
-  return JNIInvalidRefType;
-}
+  static jboolean GetBooleanField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNI_FALSE;
+  }
+
+  static jbyte GetByteField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jchar GetCharField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jshort GetShortField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jint GetIntField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jlong GetLongField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jfloat GetFloatField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jdouble GetDoubleField(JNIEnv* env, jobject obj, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static void SetObjectField(JNIEnv* env, jobject obj, jfieldID fieldID, jobject val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetBooleanField(JNIEnv* env, jobject obj, jfieldID fieldID, jboolean val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetByteField(JNIEnv* env, jobject obj, jfieldID fieldID, jbyte val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetCharField(JNIEnv* env, jobject obj, jfieldID fieldID, jchar val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetShortField(JNIEnv* env, jobject obj, jfieldID fieldID, jshort val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetIntField(JNIEnv* env, jobject obj, jfieldID fieldID, jint val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetLongField(JNIEnv* env, jobject obj, jfieldID fieldID, jlong val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetFloatField(JNIEnv* env, jobject obj, jfieldID fieldID, jfloat val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetDoubleField(JNIEnv* env, jobject obj, jfieldID fieldID, jdouble val) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jobject CallStaticObjectMethod(JNIEnv* env,
+      jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    jobject local_result = AddLocalReference<jobject>(ts, result.l);
+    va_end(ap);
+    return local_result;
+  }
+
+  static jobject CallStaticObjectMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, args);
+    return AddLocalReference<jobject>(ts, result.l);
+  }
+
+  static jobject CallStaticObjectMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    JValue result = InvokeWithJValues(ts, NULL, methodID, args);
+    return AddLocalReference<jobject>(ts, result.l);
+  }
+
+  static jboolean CallStaticBooleanMethod(JNIEnv* env,
+      jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.z;
+  }
+
+  static jboolean CallStaticBooleanMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).z;
+  }
+
+  static jboolean CallStaticBooleanMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).z;
+  }
+
+  static jbyte CallStaticByteMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.b;
+  }
+
+  static jbyte CallStaticByteMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).b;
+  }
+
+  static jbyte CallStaticByteMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).b;
+  }
+
+  static jchar CallStaticCharMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.c;
+  }
+
+  static jchar CallStaticCharMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).c;
+  }
+
+  static jchar CallStaticCharMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).c;
+  }
+
+  static jshort CallStaticShortMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.s;
+  }
+
+  static jshort CallStaticShortMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).s;
+  }
+
+  static jshort CallStaticShortMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).s;
+  }
+
+  static jint CallStaticIntMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.i;
+  }
+
+  static jint CallStaticIntMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).i;
+  }
+
+  static jint CallStaticIntMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).i;
+  }
+
+  static jlong CallStaticLongMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.j;
+  }
+
+  static jlong CallStaticLongMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).j;
+  }
+
+  static jlong CallStaticLongMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).j;
+  }
+
+  static jfloat CallStaticFloatMethod(JNIEnv* env, jclass cls, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.f;
+  }
+
+  static jfloat CallStaticFloatMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).f;
+  }
+
+  static jfloat CallStaticFloatMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).f;
+  }
+
+  static jdouble CallStaticDoubleMethod(JNIEnv* env, jclass cls, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    JValue result = InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+    return result.d;
+  }
+
+  static jdouble CallStaticDoubleMethodV(JNIEnv* env,
+      jclass clazz, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithVarArgs(ts, NULL, methodID, args).d;
+  }
+
+  static jdouble CallStaticDoubleMethodA(JNIEnv* env,
+      jclass clazz, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    return InvokeWithJValues(ts, NULL, methodID, args).d;
+  }
+
+  static void CallStaticVoidMethod(JNIEnv* env, jclass cls, jmethodID methodID, ...) {
+    ScopedJniThreadState ts(env);
+    va_list ap;
+    va_start(ap, methodID);
+    InvokeWithVarArgs(ts, NULL, methodID, ap);
+    va_end(ap);
+  }
+
+  static void CallStaticVoidMethodV(JNIEnv* env,
+      jclass cls, jmethodID methodID, va_list args) {
+    ScopedJniThreadState ts(env);
+    InvokeWithVarArgs(ts, NULL, methodID, args);
+  }
+
+  static void CallStaticVoidMethodA(JNIEnv* env,
+      jclass cls, jmethodID methodID, jvalue* args) {
+    ScopedJniThreadState ts(env);
+    InvokeWithJValues(ts, NULL, methodID, args);
+  }
+
+  static jobject GetStaticObjectField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jboolean GetStaticBooleanField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNI_FALSE;
+  }
+
+  static jbyte GetStaticByteField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jchar GetStaticCharField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jshort GetStaticShortField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jint GetStaticIntField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jlong GetStaticLongField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jfloat GetStaticFloatField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jdouble GetStaticDoubleField(JNIEnv* env, jclass clazz, jfieldID fieldID) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static void SetStaticObjectField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jobject value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticBooleanField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jboolean value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticByteField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jbyte value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticCharField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jchar value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticShortField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jshort value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticIntField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jint value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticLongField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jlong value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticFloatField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jfloat value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetStaticDoubleField(JNIEnv* env,
+      jclass clazz, jfieldID fieldID, jdouble value) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jstring NewString(JNIEnv* env, const jchar* unicode, jsize len) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jsize GetStringLength(JNIEnv* env, jstring str) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static const jchar* GetStringChars(JNIEnv* env, jstring str, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void ReleaseStringChars(JNIEnv* env, jstring str, const jchar* chars) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jstring NewStringUTF(JNIEnv* env, const char* utf) {
+    ScopedJniThreadState ts(env);
+    if (utf == NULL) {
+      return NULL;
+    }
+    String* result = String::AllocFromModifiedUtf8(utf);
+    return AddLocalReference<jstring>(ts, result);
+  }
+
+  static jsize GetStringUTFLength(JNIEnv* env, jstring str) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static const char* GetStringUTFChars(JNIEnv* env, jstring str, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void ReleaseStringUTFChars(JNIEnv* env, jstring str, const char* chars) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jsize GetArrayLength(JNIEnv* env, jarray array) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void SetObjectArrayElement(JNIEnv* env,
+      jobjectArray java_array, jsize index, jobject java_value) {
+    ScopedJniThreadState ts(env);
+    ObjectArray<Object>* array = Decode<ObjectArray<Object>*>(ts, java_array);
+    Object* value = Decode<Object*>(ts, java_value);
+    array->Set(index, value);
+  }
+
+  static jbooleanArray NewBooleanArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jbooleanArray, BooleanArray>(ts, length);
+  }
+
+  static jbyteArray NewByteArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jbyteArray, ByteArray>(ts, length);
+  }
+
+  static jcharArray NewCharArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jcharArray, CharArray>(ts, length);
+  }
+
+  static jdoubleArray NewDoubleArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jdoubleArray, DoubleArray>(ts, length);
+  }
+
+  static jfloatArray NewFloatArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jfloatArray, FloatArray>(ts, length);
+  }
+
+  static jintArray NewIntArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jintArray, IntArray>(ts, length);
+  }
+
+  static jlongArray NewLongArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jlongArray, LongArray>(ts, length);
+  }
+
+  static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_jclass, jobject initial_element) {
+    ScopedJniThreadState ts(env);
+    CHECK_GE(length, 0); // TODO: ReportJniError
+
+    // Compute the array class corresponding to the given element class.
+    Class* element_class = Decode<Class*>(ts, element_jclass);
+    std::string descriptor;
+    descriptor += "[";
+    descriptor += element_class->GetDescriptor().ToString();
+
+    // Find the class.
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    // TODO: need to get the appropriate ClassLoader.
+    Class* array_class = class_linker->FindClass(descriptor, NULL);
+    if (array_class == NULL) {
+      return NULL;
+    }
+
+    ObjectArray<Object>* result = ObjectArray<Object>::Alloc(array_class, length);
+    CHECK(initial_element == NULL);  // TODO: support initial_element
+    return AddLocalReference<jobjectArray>(ts, result);
+  }
+
+  static jshortArray NewShortArray(JNIEnv* env, jsize length) {
+    ScopedJniThreadState ts(env);
+    return NewPrimitiveArray<jshortArray, ShortArray>(ts, length);
+  }
+
+  static jboolean* GetBooleanArrayElements(JNIEnv* env,
+      jbooleanArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jbyte* GetByteArrayElements(JNIEnv* env, jbyteArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jchar* GetCharArrayElements(JNIEnv* env, jcharArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jshort* GetShortArrayElements(JNIEnv* env,
+      jshortArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jint* GetIntArrayElements(JNIEnv* env, jintArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jlong* GetLongArrayElements(JNIEnv* env, jlongArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jfloat* GetFloatArrayElements(JNIEnv* env,
+      jfloatArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jdouble* GetDoubleArrayElements(JNIEnv* env,
+      jdoubleArray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void ReleaseBooleanArrayElements(JNIEnv* env,
+      jbooleanArray array, jboolean* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseByteArrayElements(JNIEnv* env,
+      jbyteArray array, jbyte* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseCharArrayElements(JNIEnv* env,
+      jcharArray array, jchar* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseShortArrayElements(JNIEnv* env,
+      jshortArray array, jshort* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseIntArrayElements(JNIEnv* env,
+      jintArray array, jint* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseLongArrayElements(JNIEnv* env,
+      jlongArray array, jlong* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseFloatArrayElements(JNIEnv* env,
+      jfloatArray array, jfloat* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void ReleaseDoubleArrayElements(JNIEnv* env,
+      jdoubleArray array, jdouble* elems, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetBooleanArrayRegion(JNIEnv* env,
+      jbooleanArray array, jsize start, jsize l, jboolean* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetByteArrayRegion(JNIEnv* env,
+      jbyteArray array, jsize start, jsize len, jbyte* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetCharArrayRegion(JNIEnv* env,
+      jcharArray array, jsize start, jsize len, jchar* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetShortArrayRegion(JNIEnv* env,
+      jshortArray array, jsize start, jsize len, jshort* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetIntArrayRegion(JNIEnv* env,
+      jintArray array, jsize start, jsize len, jint* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetLongArrayRegion(JNIEnv* env,
+      jlongArray array, jsize start, jsize len, jlong* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetFloatArrayRegion(JNIEnv* env,
+      jfloatArray array, jsize start, jsize len, jfloat* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetDoubleArrayRegion(JNIEnv* env,
+      jdoubleArray array, jsize start, jsize len, jdouble* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetBooleanArrayRegion(JNIEnv* env,
+      jbooleanArray array, jsize start, jsize l, const jboolean* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetByteArrayRegion(JNIEnv* env,
+      jbyteArray array, jsize start, jsize len, const jbyte* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetCharArrayRegion(JNIEnv* env,
+      jcharArray array, jsize start, jsize len, const jchar* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetShortArrayRegion(JNIEnv* env,
+      jshortArray array, jsize start, jsize len, const jshort* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetIntArrayRegion(JNIEnv* env,
+      jintArray array, jsize start, jsize len, const jint* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetLongArrayRegion(JNIEnv* env,
+      jlongArray array, jsize start, jsize len, const jlong* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetFloatArrayRegion(JNIEnv* env,
+      jfloatArray array, jsize start, jsize len, const jfloat* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void SetDoubleArrayRegion(JNIEnv* env,
+      jdoubleArray array, jsize start, jsize len, const jdouble* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jint RegisterNatives(JNIEnv* env,
+      jclass clazz, const JNINativeMethod* methods, jint nMethods) {
+    ScopedJniThreadState ts(env);
+    Class* klass = Decode<Class*>(ts, clazz);
+    for(int i = 0; i < nMethods; i++) {
+      const char* name = methods[i].name;
+      const char* sig = methods[i].signature;
+
+      if (*sig == '!') {
+        // TODO: fast jni. it's too noisy to log all these.
+        ++sig;
+      }
+
+      Method* method = klass->FindDirectMethod(name, sig);
+      if (method == NULL) {
+        method = klass->FindVirtualMethod(name, sig);
+      }
+      if (method == NULL) {
+        Thread* self = Thread::Current();
+        std::string class_name = klass->GetDescriptor().ToString();
+        // TODO: pretty print method names through a single routine
+        self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+            "no method \"%s.%s%s\"",
+            class_name.c_str(), name, sig);
+        return JNI_ERR;
+      } else if (!method->IsNative()) {
+        Thread* self = Thread::Current();
+        std::string class_name = klass->GetDescriptor().ToString();
+        // TODO: pretty print method names through a single routine
+        self->ThrowNewException("Ljava/lang/NoSuchMethodError;",
+            "method \"%s.%s%s\" is not native",
+            class_name.c_str(), name, sig);
+        return JNI_ERR;
+      }
+      method->RegisterNative(methods[i].fnPtr);
+    }
+    return JNI_OK;
+  }
+
+  static jint UnregisterNatives(JNIEnv* env, jclass clazz) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jint MonitorEnter(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(WARNING);
+    return 0;
+  }
+
+  static jint MonitorExit(JNIEnv* env, jobject obj) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(WARNING);
+    return 0;
+  }
+
+  static jint GetJavaVM(JNIEnv* env, JavaVM** vm) {
+    ScopedJniThreadState ts(env);
+    Runtime* runtime = Runtime::Current();
+    if (runtime != NULL) {
+      *vm = reinterpret_cast<JavaVM*>(runtime->GetJavaVM());
+    } else {
+      *vm = NULL;
+    }
+    return (*vm != NULL) ? JNI_OK : JNI_ERR;
+  }
+
+  static void GetStringRegion(JNIEnv* env,
+      jstring str, jsize start, jsize len, jchar* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void GetStringUTFRegion(JNIEnv* env,
+      jstring str, jsize start, jsize len, char* buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static void* GetPrimitiveArrayCritical(JNIEnv* env,
+      jarray array, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void ReleasePrimitiveArrayCritical(JNIEnv* env,
+      jarray array, void* carray, jint mode) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static const jchar* GetStringCritical(JNIEnv* env, jstring s, jboolean* isCopy) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void ReleaseStringCritical(JNIEnv* env, jstring s, const jchar* cstr) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+  }
+
+  static jboolean ExceptionCheck(JNIEnv* env) {
+    ScopedJniThreadState ts(env);
+    return ts.Self()->IsExceptionPending() ? JNI_TRUE : JNI_FALSE;
+  }
+
+  static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return NULL;
+  }
+
+  static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return 0;
+  }
+
+  static jobjectRefType GetObjectRefType(JNIEnv* env, jobject jobj) {
+    ScopedJniThreadState ts(env);
+    UNIMPLEMENTED(FATAL);
+    return JNIInvalidRefType;
+  }
+};
 
 static const struct JNINativeInterface gNativeInterface = {
   NULL,  // reserved0.
   NULL,  // reserved1.
   NULL,  // reserved2.
   NULL,  // reserved3.
-  GetVersion,
-  DefineClass,
-  FindClass,
-  FromReflectedMethod,
-  FromReflectedField,
-  ToReflectedMethod,
-  GetSuperclass,
-  IsAssignableFrom,
-  ToReflectedField,
-  Throw,
-  ThrowNew,
-  ExceptionOccurred,
-  ExceptionDescribe,
-  ExceptionClear,
-  FatalError,
-  PushLocalFrame,
-  PopLocalFrame,
-  NewGlobalRef,
-  DeleteGlobalRef,
-  DeleteLocalRef,
-  IsSameObject,
-  NewLocalRef,
-  EnsureLocalCapacity,
-  AllocObject,
-  NewObject,
-  NewObjectV,
-  NewObjectA,
-  GetObjectClass,
-  IsInstanceOf,
-  GetMethodID,
-  CallObjectMethod,
-  CallObjectMethodV,
-  CallObjectMethodA,
-  CallBooleanMethod,
-  CallBooleanMethodV,
-  CallBooleanMethodA,
-  CallByteMethod,
-  CallByteMethodV,
-  CallByteMethodA,
-  CallCharMethod,
-  CallCharMethodV,
-  CallCharMethodA,
-  CallShortMethod,
-  CallShortMethodV,
-  CallShortMethodA,
-  CallIntMethod,
-  CallIntMethodV,
-  CallIntMethodA,
-  CallLongMethod,
-  CallLongMethodV,
-  CallLongMethodA,
-  CallFloatMethod,
-  CallFloatMethodV,
-  CallFloatMethodA,
-  CallDoubleMethod,
-  CallDoubleMethodV,
-  CallDoubleMethodA,
-  CallVoidMethod,
-  CallVoidMethodV,
-  CallVoidMethodA,
-  CallNonvirtualObjectMethod,
-  CallNonvirtualObjectMethodV,
-  CallNonvirtualObjectMethodA,
-  CallNonvirtualBooleanMethod,
-  CallNonvirtualBooleanMethodV,
-  CallNonvirtualBooleanMethodA,
-  CallNonvirtualByteMethod,
-  CallNonvirtualByteMethodV,
-  CallNonvirtualByteMethodA,
-  CallNonvirtualCharMethod,
-  CallNonvirtualCharMethodV,
-  CallNonvirtualCharMethodA,
-  CallNonvirtualShortMethod,
-  CallNonvirtualShortMethodV,
-  CallNonvirtualShortMethodA,
-  CallNonvirtualIntMethod,
-  CallNonvirtualIntMethodV,
-  CallNonvirtualIntMethodA,
-  CallNonvirtualLongMethod,
-  CallNonvirtualLongMethodV,
-  CallNonvirtualLongMethodA,
-  CallNonvirtualFloatMethod,
-  CallNonvirtualFloatMethodV,
-  CallNonvirtualFloatMethodA,
-  CallNonvirtualDoubleMethod,
-  CallNonvirtualDoubleMethodV,
-  CallNonvirtualDoubleMethodA,
-  CallNonvirtualVoidMethod,
-  CallNonvirtualVoidMethodV,
-  CallNonvirtualVoidMethodA,
-  GetFieldID,
-  GetObjectField,
-  GetBooleanField,
-  GetByteField,
-  GetCharField,
-  GetShortField,
-  GetIntField,
-  GetLongField,
-  GetFloatField,
-  GetDoubleField,
-  SetObjectField,
-  SetBooleanField,
-  SetByteField,
-  SetCharField,
-  SetShortField,
-  SetIntField,
-  SetLongField,
-  SetFloatField,
-  SetDoubleField,
-  GetStaticMethodID,
-  CallStaticObjectMethod,
-  CallStaticObjectMethodV,
-  CallStaticObjectMethodA,
-  CallStaticBooleanMethod,
-  CallStaticBooleanMethodV,
-  CallStaticBooleanMethodA,
-  CallStaticByteMethod,
-  CallStaticByteMethodV,
-  CallStaticByteMethodA,
-  CallStaticCharMethod,
-  CallStaticCharMethodV,
-  CallStaticCharMethodA,
-  CallStaticShortMethod,
-  CallStaticShortMethodV,
-  CallStaticShortMethodA,
-  CallStaticIntMethod,
-  CallStaticIntMethodV,
-  CallStaticIntMethodA,
-  CallStaticLongMethod,
-  CallStaticLongMethodV,
-  CallStaticLongMethodA,
-  CallStaticFloatMethod,
-  CallStaticFloatMethodV,
-  CallStaticFloatMethodA,
-  CallStaticDoubleMethod,
-  CallStaticDoubleMethodV,
-  CallStaticDoubleMethodA,
-  CallStaticVoidMethod,
-  CallStaticVoidMethodV,
-  CallStaticVoidMethodA,
-  GetStaticFieldID,
-  GetStaticObjectField,
-  GetStaticBooleanField,
-  GetStaticByteField,
-  GetStaticCharField,
-  GetStaticShortField,
-  GetStaticIntField,
-  GetStaticLongField,
-  GetStaticFloatField,
-  GetStaticDoubleField,
-  SetStaticObjectField,
-  SetStaticBooleanField,
-  SetStaticByteField,
-  SetStaticCharField,
-  SetStaticShortField,
-  SetStaticIntField,
-  SetStaticLongField,
-  SetStaticFloatField,
-  SetStaticDoubleField,
-  NewString,
-  GetStringLength,
-  GetStringChars,
-  ReleaseStringChars,
-  NewStringUTF,
-  GetStringUTFLength,
-  GetStringUTFChars,
-  ReleaseStringUTFChars,
-  GetArrayLength,
-  NewObjectArray,
-  GetObjectArrayElement,
-  SetObjectArrayElement,
-  NewBooleanArray,
-  NewByteArray,
-  NewCharArray,
-  NewShortArray,
-  NewIntArray,
-  NewLongArray,
-  NewFloatArray,
-  NewDoubleArray,
-  GetBooleanArrayElements,
-  GetByteArrayElements,
-  GetCharArrayElements,
-  GetShortArrayElements,
-  GetIntArrayElements,
-  GetLongArrayElements,
-  GetFloatArrayElements,
-  GetDoubleArrayElements,
-  ReleaseBooleanArrayElements,
-  ReleaseByteArrayElements,
-  ReleaseCharArrayElements,
-  ReleaseShortArrayElements,
-  ReleaseIntArrayElements,
-  ReleaseLongArrayElements,
-  ReleaseFloatArrayElements,
-  ReleaseDoubleArrayElements,
-  GetBooleanArrayRegion,
-  GetByteArrayRegion,
-  GetCharArrayRegion,
-  GetShortArrayRegion,
-  GetIntArrayRegion,
-  GetLongArrayRegion,
-  GetFloatArrayRegion,
-  GetDoubleArrayRegion,
-  SetBooleanArrayRegion,
-  SetByteArrayRegion,
-  SetCharArrayRegion,
-  SetShortArrayRegion,
-  SetIntArrayRegion,
-  SetLongArrayRegion,
-  SetFloatArrayRegion,
-  SetDoubleArrayRegion,
-  RegisterNatives,
-  UnregisterNatives,
-  MonitorEnter,
-  MonitorExit,
-  GetJavaVM,
-  GetStringRegion,
-  GetStringUTFRegion,
-  GetPrimitiveArrayCritical,
-  ReleasePrimitiveArrayCritical,
-  GetStringCritical,
-  ReleaseStringCritical,
-  NewWeakGlobalRef,
-  DeleteWeakGlobalRef,
-  ExceptionCheck,
-  NewDirectByteBuffer,
-  GetDirectBufferAddress,
-  GetDirectBufferCapacity,
-  GetObjectRefType,
+  JNI::GetVersion,
+  JNI::DefineClass,
+  JNI::FindClass,
+  JNI::FromReflectedMethod,
+  JNI::FromReflectedField,
+  JNI::ToReflectedMethod,
+  JNI::GetSuperclass,
+  JNI::IsAssignableFrom,
+  JNI::ToReflectedField,
+  JNI::Throw,
+  JNI::ThrowNew,
+  JNI::ExceptionOccurred,
+  JNI::ExceptionDescribe,
+  JNI::ExceptionClear,
+  JNI::FatalError,
+  JNI::PushLocalFrame,
+  JNI::PopLocalFrame,
+  JNI::NewGlobalRef,
+  JNI::DeleteGlobalRef,
+  JNI::DeleteLocalRef,
+  JNI::IsSameObject,
+  JNI::NewLocalRef,
+  JNI::EnsureLocalCapacity,
+  JNI::AllocObject,
+  JNI::NewObject,
+  JNI::NewObjectV,
+  JNI::NewObjectA,
+  JNI::GetObjectClass,
+  JNI::IsInstanceOf,
+  JNI::GetMethodID,
+  JNI::CallObjectMethod,
+  JNI::CallObjectMethodV,
+  JNI::CallObjectMethodA,
+  JNI::CallBooleanMethod,
+  JNI::CallBooleanMethodV,
+  JNI::CallBooleanMethodA,
+  JNI::CallByteMethod,
+  JNI::CallByteMethodV,
+  JNI::CallByteMethodA,
+  JNI::CallCharMethod,
+  JNI::CallCharMethodV,
+  JNI::CallCharMethodA,
+  JNI::CallShortMethod,
+  JNI::CallShortMethodV,
+  JNI::CallShortMethodA,
+  JNI::CallIntMethod,
+  JNI::CallIntMethodV,
+  JNI::CallIntMethodA,
+  JNI::CallLongMethod,
+  JNI::CallLongMethodV,
+  JNI::CallLongMethodA,
+  JNI::CallFloatMethod,
+  JNI::CallFloatMethodV,
+  JNI::CallFloatMethodA,
+  JNI::CallDoubleMethod,
+  JNI::CallDoubleMethodV,
+  JNI::CallDoubleMethodA,
+  JNI::CallVoidMethod,
+  JNI::CallVoidMethodV,
+  JNI::CallVoidMethodA,
+  JNI::CallNonvirtualObjectMethod,
+  JNI::CallNonvirtualObjectMethodV,
+  JNI::CallNonvirtualObjectMethodA,
+  JNI::CallNonvirtualBooleanMethod,
+  JNI::CallNonvirtualBooleanMethodV,
+  JNI::CallNonvirtualBooleanMethodA,
+  JNI::CallNonvirtualByteMethod,
+  JNI::CallNonvirtualByteMethodV,
+  JNI::CallNonvirtualByteMethodA,
+  JNI::CallNonvirtualCharMethod,
+  JNI::CallNonvirtualCharMethodV,
+  JNI::CallNonvirtualCharMethodA,
+  JNI::CallNonvirtualShortMethod,
+  JNI::CallNonvirtualShortMethodV,
+  JNI::CallNonvirtualShortMethodA,
+  JNI::CallNonvirtualIntMethod,
+  JNI::CallNonvirtualIntMethodV,
+  JNI::CallNonvirtualIntMethodA,
+  JNI::CallNonvirtualLongMethod,
+  JNI::CallNonvirtualLongMethodV,
+  JNI::CallNonvirtualLongMethodA,
+  JNI::CallNonvirtualFloatMethod,
+  JNI::CallNonvirtualFloatMethodV,
+  JNI::CallNonvirtualFloatMethodA,
+  JNI::CallNonvirtualDoubleMethod,
+  JNI::CallNonvirtualDoubleMethodV,
+  JNI::CallNonvirtualDoubleMethodA,
+  JNI::CallNonvirtualVoidMethod,
+  JNI::CallNonvirtualVoidMethodV,
+  JNI::CallNonvirtualVoidMethodA,
+  JNI::GetFieldID,
+  JNI::GetObjectField,
+  JNI::GetBooleanField,
+  JNI::GetByteField,
+  JNI::GetCharField,
+  JNI::GetShortField,
+  JNI::GetIntField,
+  JNI::GetLongField,
+  JNI::GetFloatField,
+  JNI::GetDoubleField,
+  JNI::SetObjectField,
+  JNI::SetBooleanField,
+  JNI::SetByteField,
+  JNI::SetCharField,
+  JNI::SetShortField,
+  JNI::SetIntField,
+  JNI::SetLongField,
+  JNI::SetFloatField,
+  JNI::SetDoubleField,
+  JNI::GetStaticMethodID,
+  JNI::CallStaticObjectMethod,
+  JNI::CallStaticObjectMethodV,
+  JNI::CallStaticObjectMethodA,
+  JNI::CallStaticBooleanMethod,
+  JNI::CallStaticBooleanMethodV,
+  JNI::CallStaticBooleanMethodA,
+  JNI::CallStaticByteMethod,
+  JNI::CallStaticByteMethodV,
+  JNI::CallStaticByteMethodA,
+  JNI::CallStaticCharMethod,
+  JNI::CallStaticCharMethodV,
+  JNI::CallStaticCharMethodA,
+  JNI::CallStaticShortMethod,
+  JNI::CallStaticShortMethodV,
+  JNI::CallStaticShortMethodA,
+  JNI::CallStaticIntMethod,
+  JNI::CallStaticIntMethodV,
+  JNI::CallStaticIntMethodA,
+  JNI::CallStaticLongMethod,
+  JNI::CallStaticLongMethodV,
+  JNI::CallStaticLongMethodA,
+  JNI::CallStaticFloatMethod,
+  JNI::CallStaticFloatMethodV,
+  JNI::CallStaticFloatMethodA,
+  JNI::CallStaticDoubleMethod,
+  JNI::CallStaticDoubleMethodV,
+  JNI::CallStaticDoubleMethodA,
+  JNI::CallStaticVoidMethod,
+  JNI::CallStaticVoidMethodV,
+  JNI::CallStaticVoidMethodA,
+  JNI::GetStaticFieldID,
+  JNI::GetStaticObjectField,
+  JNI::GetStaticBooleanField,
+  JNI::GetStaticByteField,
+  JNI::GetStaticCharField,
+  JNI::GetStaticShortField,
+  JNI::GetStaticIntField,
+  JNI::GetStaticLongField,
+  JNI::GetStaticFloatField,
+  JNI::GetStaticDoubleField,
+  JNI::SetStaticObjectField,
+  JNI::SetStaticBooleanField,
+  JNI::SetStaticByteField,
+  JNI::SetStaticCharField,
+  JNI::SetStaticShortField,
+  JNI::SetStaticIntField,
+  JNI::SetStaticLongField,
+  JNI::SetStaticFloatField,
+  JNI::SetStaticDoubleField,
+  JNI::NewString,
+  JNI::GetStringLength,
+  JNI::GetStringChars,
+  JNI::ReleaseStringChars,
+  JNI::NewStringUTF,
+  JNI::GetStringUTFLength,
+  JNI::GetStringUTFChars,
+  JNI::ReleaseStringUTFChars,
+  JNI::GetArrayLength,
+  JNI::NewObjectArray,
+  JNI::GetObjectArrayElement,
+  JNI::SetObjectArrayElement,
+  JNI::NewBooleanArray,
+  JNI::NewByteArray,
+  JNI::NewCharArray,
+  JNI::NewShortArray,
+  JNI::NewIntArray,
+  JNI::NewLongArray,
+  JNI::NewFloatArray,
+  JNI::NewDoubleArray,
+  JNI::GetBooleanArrayElements,
+  JNI::GetByteArrayElements,
+  JNI::GetCharArrayElements,
+  JNI::GetShortArrayElements,
+  JNI::GetIntArrayElements,
+  JNI::GetLongArrayElements,
+  JNI::GetFloatArrayElements,
+  JNI::GetDoubleArrayElements,
+  JNI::ReleaseBooleanArrayElements,
+  JNI::ReleaseByteArrayElements,
+  JNI::ReleaseCharArrayElements,
+  JNI::ReleaseShortArrayElements,
+  JNI::ReleaseIntArrayElements,
+  JNI::ReleaseLongArrayElements,
+  JNI::ReleaseFloatArrayElements,
+  JNI::ReleaseDoubleArrayElements,
+  JNI::GetBooleanArrayRegion,
+  JNI::GetByteArrayRegion,
+  JNI::GetCharArrayRegion,
+  JNI::GetShortArrayRegion,
+  JNI::GetIntArrayRegion,
+  JNI::GetLongArrayRegion,
+  JNI::GetFloatArrayRegion,
+  JNI::GetDoubleArrayRegion,
+  JNI::SetBooleanArrayRegion,
+  JNI::SetByteArrayRegion,
+  JNI::SetCharArrayRegion,
+  JNI::SetShortArrayRegion,
+  JNI::SetIntArrayRegion,
+  JNI::SetLongArrayRegion,
+  JNI::SetFloatArrayRegion,
+  JNI::SetDoubleArrayRegion,
+  JNI::RegisterNatives,
+  JNI::UnregisterNatives,
+  JNI::MonitorEnter,
+  JNI::MonitorExit,
+  JNI::GetJavaVM,
+  JNI::GetStringRegion,
+  JNI::GetStringUTFRegion,
+  JNI::GetPrimitiveArrayCritical,
+  JNI::ReleasePrimitiveArrayCritical,
+  JNI::GetStringCritical,
+  JNI::ReleaseStringCritical,
+  JNI::NewWeakGlobalRef,
+  JNI::DeleteWeakGlobalRef,
+  JNI::ExceptionCheck,
+  JNI::NewDirectByteBuffer,
+  JNI::GetDirectBufferAddress,
+  JNI::GetDirectBufferCapacity,
+  JNI::GetObjectRefType,
 };
 
 static const size_t kMonitorsInitial = 32; // Arbitrary.
@@ -2539,92 +2404,95 @@
   return JNI_ERR;
 }
 
-jint DestroyJavaVM(JavaVM* vm) {
-  if (vm == NULL) {
-    return JNI_ERR;
-  } else {
-    JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
-    delete raw_vm->runtime;
-    return JNI_OK;
+class JII {
+ public:
+  static jint DestroyJavaVM(JavaVM* vm) {
+    if (vm == NULL) {
+      return JNI_ERR;
+    } else {
+      JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
+      delete raw_vm->runtime;
+      return JNI_OK;
+    }
   }
-}
 
-jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-  if (vm == NULL || p_env == NULL) {
-    return JNI_ERR;
-  }
-  JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
-  Runtime* runtime = raw_vm->runtime;
-  const char* name = NULL;
-  if (thr_args != NULL) {
-    // TODO: check version
-    name = static_cast<JavaVMAttachArgs*>(thr_args)->name;
-    // TODO: thread group
-  }
-  bool success = runtime->AttachCurrentThread(name, p_env);
-  if (!success) {
-    return JNI_ERR;
-  } else {
-    return JNI_OK;
-  }
-}
-
-jint DetachCurrentThread(JavaVM* vm) {
-  if (vm == NULL) {
-    return JNI_ERR;
-  } else {
+  static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
+    if (vm == NULL || p_env == NULL) {
+      return JNI_ERR;
+    }
     JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
     Runtime* runtime = raw_vm->runtime;
-    runtime->DetachCurrentThread();
+    const char* name = NULL;
+    if (thr_args != NULL) {
+      // TODO: check version
+      name = static_cast<JavaVMAttachArgs*>(thr_args)->name;
+      // TODO: thread group
+    }
+    bool success = runtime->AttachCurrentThread(name, p_env);
+    if (!success) {
+      return JNI_ERR;
+    } else {
+      return JNI_OK;
+    }
+  }
+
+  static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
+    if (vm == NULL || p_env == NULL) {
+      return JNI_ERR;
+    }
+    JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
+    Runtime* runtime = raw_vm->runtime;
+    const char* name = NULL;
+    if (thr_args != NULL) {
+      // TODO: check version
+      name = static_cast<JavaVMAttachArgs*>(thr_args)->name;
+      // TODO: thread group
+    }
+    bool success = runtime->AttachCurrentThreadAsDaemon(name, p_env);
+    if (!success) {
+      return JNI_ERR;
+    } else {
+      return JNI_OK;
+    }
+  }
+
+  static jint DetachCurrentThread(JavaVM* vm) {
+    if (vm == NULL) {
+      return JNI_ERR;
+    } else {
+      JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
+      Runtime* runtime = raw_vm->runtime;
+      runtime->DetachCurrentThread();
+      return JNI_OK;
+    }
+  }
+
+  static jint GetEnv(JavaVM* vm, void** env, jint version) {
+    if (version < JNI_VERSION_1_1 || version > JNI_VERSION_1_6) {
+      return JNI_EVERSION;
+    }
+    if (vm == NULL || env == NULL) {
+      return JNI_ERR;
+    }
+    Thread* thread = Thread::Current();
+    if (thread == NULL) {
+      *env = NULL;
+      return JNI_EDETACHED;
+    }
+    *env = thread->GetJniEnv();
     return JNI_OK;
   }
-}
-
-jint GetEnv(JavaVM* vm, void** env, jint version) {
-  if (version < JNI_VERSION_1_1 || version > JNI_VERSION_1_6) {
-    return JNI_EVERSION;
-  }
-  if (vm == NULL || env == NULL) {
-    return JNI_ERR;
-  }
-  Thread* thread = Thread::Current();
-  if (thread == NULL) {
-    *env = NULL;
-    return JNI_EDETACHED;
-  }
-  *env = thread->GetJniEnv();
-  return JNI_OK;
-}
-
-jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-  if (vm == NULL || p_env == NULL) {
-    return JNI_ERR;
-  }
-  JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
-  Runtime* runtime = raw_vm->runtime;
-  const char* name = NULL;
-  if (thr_args != NULL) {
-    // TODO: check version
-    name = static_cast<JavaVMAttachArgs*>(thr_args)->name;
-    // TODO: thread group
-  }
-  bool success = runtime->AttachCurrentThreadAsDaemon(name, p_env);
-  if (!success) {
-    return JNI_ERR;
-  } else {
-    return JNI_OK;
-  }
-}
+};
 
 struct JNIInvokeInterface gInvokeInterface = {
   NULL,  // reserved0
   NULL,  // reserved1
   NULL,  // reserved2
-  DestroyJavaVM,
-  AttachCurrentThread,
-  DetachCurrentThread,
-  GetEnv,
-  AttachCurrentThreadAsDaemon
+  JII::DestroyJavaVM,
+  JII::AttachCurrentThread,
+  JII::DetachCurrentThread,
+  JII::GetEnv,
+  JII::AttachCurrentThreadAsDaemon
 };
 
 static const size_t kPinTableInitialSize = 16;
@@ -2653,4 +2521,158 @@
   delete weak_globals_lock;
 }
 
+/*
+ * Load native code from the specified absolute pathname.  Per the spec,
+ * if we've already loaded a library with the specified pathname, we
+ * return without doing anything.
+ *
+ * TODO? for better results we should absolutify the pathname.  For fully
+ * correct results we should stat to get the inode and compare that.  The
+ * existing implementation is fine so long as everybody is using
+ * System.loadLibrary.
+ *
+ * The library will be associated with the specified class loader.  The JNI
+ * spec says we can't load the same library into more than one class loader.
+ *
+ * Returns "true" on success. On failure, sets *detail to a
+ * human-readable description of the error or NULL if no detail is
+ * available; ownership of the string is transferred to the caller.
+ */
+bool JavaVMExt::LoadNativeLibrary(const std::string& path, Object* class_loader, char** detail) {
+  *detail = NULL;
+
+  // See if we've already loaded this library.  If we have, and the class loader
+  // matches, return successfully without doing anything.
+  SharedLibrary* library = libraries[path];
+  if (library != NULL) {
+    if (library->GetClassLoader() != class_loader) {
+      LOG(WARNING) << "Shared library \"" << path << "\" already opened by "
+                   << "ClassLoader " << library->GetClassLoader() << "; "
+                   << "can't open in " << class_loader;
+      *detail = strdup("already opened by different ClassLoader");
+      return false;
+    }
+    if (verbose_jni) {
+      LOG(INFO) << "[Shared library \"" << path << "\" already loaded in "
+                << "ClassLoader " << class_loader << "]";
+    }
+    if (!library->CheckOnLoadResult(this)) {
+      *detail = strdup("JNI_OnLoad failed before");
+      return false;
+    }
+    return true;
+  }
+
+  // Open the shared library.  Because we're using a full path, the system
+  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
+  // resolve this library's dependencies though.)
+
+  // Failures here are expected when java.library.path has several entries
+  // and we have to hunt for the lib.
+
+  // The current version of the dynamic linker prints detailed information
+  // about dlopen() failures.  Some things to check if the message is
+  // cryptic:
+  //   - make sure the library exists on the device
+  //   - verify that the right path is being opened (the debug log message
+  //     above can help with that)
+  //   - check to see if the library is valid (e.g. not zero bytes long)
+  //   - check config/prelink-linux-arm.map to ensure that the library
+  //     is listed and is not being overrun by the previous entry (if
+  //     loading suddenly stops working on a prelinked library, this is
+  //     a good one to check)
+  //   - write a trivial app that calls sleep() then dlopen(), attach
+  //     to it with "strace -p <pid>" while it sleeps, and watch for
+  //     attempts to open nonexistent dependent shared libs
+
+  // TODO: automate some of these checks!
+
+  // This can execute slowly for a large library on a busy system, so we
+  // want to switch from RUNNING to VMWAIT while it executes.  This allows
+  // the GC to ignore us.
+  Thread* self = Thread::Current();
+  Thread::State old_state = self->GetState();
+  self->SetState(Thread::kWaiting); // TODO: VMWAIT
+  void* handle = dlopen(path.c_str(), RTLD_LAZY);
+  self->SetState(old_state);
+
+  if (verbose_jni) {
+    LOG(INFO) << "[Call to dlopen(\"" << path << "\") returned " << handle << "]";
+  }
+
+  if (handle == NULL) {
+    *detail = strdup(dlerror());
+    return false;
+  }
+
+  // Create a new entry.
+  library = new SharedLibrary(path, handle, class_loader);
+  UNIMPLEMENTED(ERROR) << "missing pthread_cond_init";
+  // pthread_cond_init(&library->onLoadCond, NULL);
+
+  libraries[path] = library;
+
+  //  if (pNewEntry != pActualEntry) {
+  //    LOG(INFO) << "WOW: we lost a race to add a shared library (\"" << path << "\" ClassLoader=" << class_loader <<")";
+  //    freeSharedLibEntry(pNewEntry);
+  //    return CheckOnLoadResult(this, pActualEntry);
+  //  } else
+  {
+    if (verbose_jni) {
+      LOG(INFO) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
+    }
+
+    bool result = true;
+    void* sym = dlsym(handle, "JNI_OnLoad");
+    if (sym == NULL) {
+      if (verbose_jni) {
+        LOG(INFO) << "[No JNI_OnLoad found in \"" << path << "\"]";
+      }
+    } else {
+      // Call JNI_OnLoad.  We have to override the current class
+      // loader, which will always be "null" since the stuff at the
+      // top of the stack is around Runtime.loadLibrary().  (See
+      // the comments in the JNI FindClass function.)
+      UNIMPLEMENTED(WARNING) << "need to override current class loader";
+      typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
+      JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
+      //Object* prevOverride = self->classLoaderOverride;
+      //self->classLoaderOverride = classLoader;
+
+      old_state = self->GetState();
+      self->SetState(Thread::kNative);
+      if (verbose_jni) {
+        LOG(INFO) << "[Calling JNI_OnLoad in \"" << path << "\"]";
+      }
+      int version = (*jni_on_load)(reinterpret_cast<JavaVM*>(this), NULL);
+      self->SetState(old_state);
+
+      UNIMPLEMENTED(WARNING) << "need to restore current class loader";
+      //self->classLoaderOverride = prevOverride;
+
+      if (version != JNI_VERSION_1_2 &&
+      version != JNI_VERSION_1_4 &&
+      version != JNI_VERSION_1_6) {
+        LOG(WARNING) << "JNI_OnLoad in \"" << path << "\" returned "
+                     << "bad version: " << version;
+        // It's unwise to call dlclose() here, but we can mark it
+        // as bad and ensure that future load attempts will fail.
+        // We don't know how far JNI_OnLoad got, so there could
+        // be some partially-initialized stuff accessible through
+        // newly-registered native method calls.  We could try to
+        // unregister them, but that doesn't seem worthwhile.
+        result = false;
+      } else {
+        if (verbose_jni) {
+          LOG(INFO) << "[Returned " << (result ? "successfully" : "failure")
+                    << " from JNI_OnLoad in \"" << path << "\"]";
+        }
+      }
+    }
+
+    library->SetResult(result);
+    return result;
+  }
+}
+
 }  // namespace art
diff --git a/src/jni_internal_test.cc b/src/jni_internal_test.cc
index f51b6c9..6c5d6f2 100644
--- a/src/jni_internal_test.cc
+++ b/src/jni_internal_test.cc
@@ -57,23 +57,91 @@
   EXPECT_CLASS_NOT_FOUND("K");
 }
 
+#define EXPECT_EXCEPTION(exception_class) \
+  do { \
+    EXPECT_TRUE(env_->ExceptionCheck()); \
+    jthrowable exception = env_->ExceptionOccurred(); \
+    EXPECT_NE(static_cast<jthrowable>(NULL), exception); \
+    EXPECT_TRUE(env_->IsInstanceOf(exception, exception_class)); \
+    env_->ExceptionClear(); \
+  } while (false)
+
+TEST_F(JniInternalTest, GetFieldID) {
+  jclass jlnsfe = env_->FindClass("java/lang/NoSuchFieldError");
+  ASSERT_TRUE(jlnsfe != NULL);
+  jclass c = env_->FindClass("java/lang/String");
+  ASSERT_TRUE(c != NULL);
+
+  // Wrong type.
+  jfieldID fid = env_->GetFieldID(c, "count", "J");
+  EXPECT_EQ(static_cast<jfieldID>(NULL), fid);
+  EXPECT_EXCEPTION(jlnsfe);
+
+  // Wrong name.
+  fid = env_->GetFieldID(c, "Count", "I");
+  EXPECT_EQ(static_cast<jfieldID>(NULL), fid);
+  EXPECT_EXCEPTION(jlnsfe);
+
+  // Good declared field lookup.
+  fid = env_->GetFieldID(c, "count", "I");
+  EXPECT_NE(static_cast<jfieldID>(NULL), fid);
+  EXPECT_TRUE(fid != NULL);
+  EXPECT_FALSE(env_->ExceptionCheck());
+
+  // Good superclass field lookup.
+  c = env_->FindClass("java/lang/StringBuilder");
+  fid = env_->GetFieldID(c, "count", "I");
+  EXPECT_NE(static_cast<jfieldID>(NULL), fid);
+  EXPECT_TRUE(fid != NULL);
+  EXPECT_FALSE(env_->ExceptionCheck());
+
+  // Not instance.
+  fid = env_->GetFieldID(c, "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
+  EXPECT_EQ(static_cast<jfieldID>(NULL), fid);
+  EXPECT_EXCEPTION(jlnsfe);
+}
+
+TEST_F(JniInternalTest, GetStaticFieldID) {
+  jclass jlnsfe = env_->FindClass("java/lang/NoSuchFieldError");
+  ASSERT_TRUE(jlnsfe != NULL);
+  jclass c = env_->FindClass("java/lang/String");
+  ASSERT_TRUE(c != NULL);
+
+  // Wrong type.
+  jfieldID fid = env_->GetStaticFieldID(c, "CASE_INSENSITIVE_ORDER", "J");
+  EXPECT_EQ(static_cast<jfieldID>(NULL), fid);
+  EXPECT_EXCEPTION(jlnsfe);
+
+  // Wrong name.
+  fid = env_->GetStaticFieldID(c, "cASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
+  EXPECT_EQ(static_cast<jfieldID>(NULL), fid);
+  EXPECT_EXCEPTION(jlnsfe);
+
+  // Good declared field lookup.
+  fid = env_->GetStaticFieldID(c, "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
+  EXPECT_NE(static_cast<jfieldID>(NULL), fid);
+  EXPECT_TRUE(fid != NULL);
+  EXPECT_FALSE(env_->ExceptionCheck());
+
+  // Not static.
+  fid = env_->GetStaticFieldID(c, "count", "I");
+  EXPECT_EQ(static_cast<jfieldID>(NULL), fid);
+  EXPECT_EXCEPTION(jlnsfe);
+}
+
 TEST_F(JniInternalTest, GetMethodID) {
   jclass jlobject = env_->FindClass("java/lang/Object");
   jclass jlstring = env_->FindClass("java/lang/String");
   jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
 
   // Sanity check that no exceptions are pending
-  EXPECT_FALSE(env_->ExceptionCheck());
+  ASSERT_FALSE(env_->ExceptionCheck());
 
   // Check that java.lang.Object.foo() doesn't exist and NoSuchMethodError is
   // a pending exception
   jmethodID method = env_->GetMethodID(jlobject, "foo", "()V");
   EXPECT_EQ(static_cast<jmethodID>(NULL), method);
-  EXPECT_TRUE(env_->ExceptionCheck());
-  jthrowable exception = env_->ExceptionOccurred();
-  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
-  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(jlnsme);
 
   // Check that java.lang.Object.equals() does exist
   method = env_->GetMethodID(jlobject, "equals", "(Ljava/lang/Object;)Z");
@@ -84,11 +152,7 @@
   // method is static
   method = env_->GetMethodID(jlstring, "valueOf", "(I)Ljava/lang/String;");
   EXPECT_EQ(static_cast<jmethodID>(NULL), method);
-  EXPECT_TRUE(env_->ExceptionCheck());
-  exception = env_->ExceptionOccurred();
-  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
-  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(jlnsme);
 }
 
 TEST_F(JniInternalTest, GetStaticMethodID) {
@@ -96,27 +160,19 @@
   jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
 
   // Sanity check that no exceptions are pending
-  EXPECT_FALSE(env_->ExceptionCheck());
+  ASSERT_FALSE(env_->ExceptionCheck());
 
   // Check that java.lang.Object.foo() doesn't exist and NoSuchMethodError is
   // a pending exception
   jmethodID method = env_->GetStaticMethodID(jlobject, "foo", "()V");
   EXPECT_EQ(static_cast<jmethodID>(NULL), method);
-  EXPECT_TRUE(env_->ExceptionCheck());
-  jthrowable exception = env_->ExceptionOccurred();
-  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
-  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(jlnsme);
 
   // Check that GetStaticMethodID for java.lang.Object.equals(Object) fails as
   // the method is not static
   method = env_->GetStaticMethodID(jlobject, "equals", "(Ljava/lang/Object;)Z");
   EXPECT_EQ(static_cast<jmethodID>(NULL), method);
-  EXPECT_TRUE(env_->ExceptionCheck());
-  exception = env_->ExceptionOccurred();
-  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
-  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(jlnsme);
 
   // Check that java.lang.String.valueOf(int) does exist
   jclass jlstring = env_->FindClass("java/lang/String");
@@ -126,12 +182,42 @@
   EXPECT_FALSE(env_->ExceptionCheck());
 }
 
+TEST_F(JniInternalTest, FromReflectedField_ToReflectedField) {
+  jclass jlrField = env_->FindClass("java/lang/reflect/Field");
+  jclass c = env_->FindClass("java/lang/String");
+  ASSERT_TRUE(c != NULL);
+  jfieldID fid = env_->GetFieldID(c, "count", "I");
+  ASSERT_TRUE(fid != NULL);
+  // Turn the fid into a java.lang.reflect.Field...
+  jobject field = env_->ToReflectedField(c, fid, JNI_FALSE);
+  ASSERT_TRUE(c != NULL);
+  ASSERT_TRUE(env_->IsInstanceOf(field, jlrField));
+  // ...and back again.
+  jfieldID fid2 = env_->FromReflectedField(field);
+  ASSERT_TRUE(fid2 != NULL);
+}
+
+TEST_F(JniInternalTest, FromReflectedMethod_ToReflectedMethod) {
+  jclass jlrMethod = env_->FindClass("java/lang/reflect/Method");
+  jclass c = env_->FindClass("java/lang/String");
+  ASSERT_TRUE(c != NULL);
+  jmethodID mid = env_->GetMethodID(c, "length", "()I");
+  ASSERT_TRUE(mid != NULL);
+  // Turn the mid into a java.lang.reflect.Method...
+  jobject method = env_->ToReflectedMethod(c, mid, JNI_FALSE);
+  ASSERT_TRUE(c != NULL);
+  ASSERT_TRUE(env_->IsInstanceOf(method, jlrMethod));
+  // ...and back again.
+  jmethodID mid2 = env_->FromReflectedMethod(method);
+  ASSERT_TRUE(mid2 != NULL);
+}
+
 TEST_F(JniInternalTest, RegisterNatives) {
   jclass jlobject = env_->FindClass("java/lang/Object");
   jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
 
   // Sanity check that no exceptions are pending
-  EXPECT_FALSE(env_->ExceptionCheck());
+  ASSERT_FALSE(env_->ExceptionCheck());
 
   // Check that registering to a non-existent java.lang.Object.foo() causes a
   // NoSuchMethodError
@@ -139,22 +225,14 @@
     JNINativeMethod methods[] = {{"foo", "()V", NULL}};
     env_->RegisterNatives(jlobject, methods, 1);
   }
-  EXPECT_TRUE(env_->ExceptionCheck());
-  jthrowable exception = env_->ExceptionOccurred();
-  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
-  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(jlnsme);
 
   // Check that registering non-native methods causes a NoSuchMethodError
   {
     JNINativeMethod methods[] = {{"equals", "(Ljava/lang/Object;)Z", NULL}};
     env_->RegisterNatives(jlobject, methods, 1);
   }
-  EXPECT_TRUE(env_->ExceptionCheck());
-  exception = env_->ExceptionOccurred();
-  EXPECT_NE(static_cast<jthrowable>(NULL), exception);
-  EXPECT_TRUE(env_->IsInstanceOf(exception, jlnsme));
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(jlnsme);
 
   // Check that registering native methods is successful
   {
@@ -215,6 +293,7 @@
 }
 
 TEST_F(JniInternalTest, SetObjectArrayElement) {
+  jclass aioobe = env_->FindClass("java/lang/ArrayIndexOutOfBoundsException");
   jclass c = env_->FindClass("[Ljava/lang/Object;");
   ASSERT_TRUE(c != NULL);
 
@@ -224,16 +303,12 @@
   // TODO: check reading value back
 
   // ArrayIndexOutOfBounds for negative index.
-  // TODO: check exception type
   env_->SetObjectArrayElement(array, -1, c);
-  EXPECT_TRUE(env_->ExceptionCheck());
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(aioobe);
 
   // ArrayIndexOutOfBounds for too-large index.
-  // TODO: check exception type
   env_->SetObjectArrayElement(array, 1, c);
-  EXPECT_TRUE(env_->ExceptionCheck());
-  env_->ExceptionClear();
+  EXPECT_EXCEPTION(aioobe);
 
   // TODO: check ArrayStoreException thrown for bad types.
 }
diff --git a/src/object.cc b/src/object.cc
index 8af1778..b9324a6 100644
--- a/src/object.cc
+++ b/src/object.cc
@@ -442,6 +442,62 @@
   return NULL;
 }
 
+Field* Class::FindDeclaredInstanceField(const StringPiece& name, const StringPiece& descriptor) {
+  // Is the field in this class?
+  // Interfaces are not relevant because they can't contain instance fields.
+  for (size_t i = 0; i < NumInstanceFields(); ++i) {
+    Field* f = GetInstanceField(i);
+    if (f->GetName()->Equals(name) && f->GetDescriptor() == descriptor) {
+      return f;
+    }
+  }
+  return NULL;
+}
+
+Field* Class::FindInstanceField(const StringPiece& name, const StringPiece& descriptor) {
+  // Is the field in this class, or any of its superclasses?
+  // Interfaces are not relevant because they can't contain instance fields.
+  for (Class* c = this; c != NULL; c = c->GetSuperClass()) {
+    Field* f = c->FindDeclaredInstanceField(name, descriptor);
+    if (f != NULL) {
+      return f;
+    }
+  }
+  return NULL;
+}
+
+Field* Class::FindDeclaredStaticField(const StringPiece& name, const StringPiece& descriptor) {
+  for (size_t i = 0; i < NumStaticFields(); ++i) {
+    Field* f = GetStaticField(i);
+    if (f->GetName()->Equals(name) && f->GetDescriptor() == descriptor) {
+      return f;
+    }
+  }
+  return NULL;
+}
+
+Field* Class::FindStaticField(const StringPiece& name, const StringPiece& descriptor) {
+  // Is the field in this class (or its interfaces), or any of its
+  // superclasses (or their interfaces)?
+  for (Class* c = this; c != NULL; c = c->GetSuperClass()) {
+    // Is the field in this class?
+    Field* f = c->FindDeclaredStaticField(name, descriptor);
+    if (f != NULL) {
+      return f;
+    }
+
+    // Is this field in any of this class' interfaces?
+    for (size_t i = 0; i < c->NumInterfaces(); ++i) {
+      Class* interface = c->GetInterface(i);
+      f = interface->FindDeclaredStaticField(name, descriptor);
+      if (f != NULL) {
+        return f;
+      }
+    }
+  }
+  return NULL;
+}
+
 template<typename T>
 PrimitiveArray<T>* PrimitiveArray<T>::Alloc(size_t length) {
   Array* raw_array = Array::Alloc(array_class_, length, sizeof(T));
diff --git a/src/object.h b/src/object.h
index c78103b..2b5a054 100644
--- a/src/object.h
+++ b/src/object.h
@@ -979,6 +979,20 @@
     return num_reference_instance_fields_;
   }
 
+  // Finds the given instance field in this class or a superclass.
+  Field* FindInstanceField(const StringPiece& name,
+      const StringPiece& descriptor);
+
+  Field* FindDeclaredInstanceField(const StringPiece& name,
+      const StringPiece& descriptor);
+
+  // Finds the given static field in this class or a superclass.
+  Field* FindStaticField(const StringPiece& name,
+      const StringPiece& descriptor);
+
+  Field* FindDeclaredStaticField(const StringPiece& name,
+      const StringPiece& descriptor);
+
   Field* GetInstanceField(uint32_t i) const {  // TODO: uint16_t
     DCHECK_NE(NumInstanceFields(), 0U);
     return ifields_->Get(i);
diff --git a/src/object_test.cc b/src/object_test.cc
index 3f5333b..549544c 100644
--- a/src/object_test.cc
+++ b/src/object_test.cc
@@ -333,4 +333,62 @@
   EXPECT_TRUE(O->IsAssignableFrom(IA));
 }
 
+TEST_F(ObjectTest, FindInstanceField) {
+  String* s = String::AllocFromModifiedUtf8("ABC");
+  ASSERT_TRUE(s != NULL);
+  Class* c = s->GetClass();
+  ASSERT_TRUE(c != NULL);
+
+  // Wrong type.
+  EXPECT_TRUE(c->FindDeclaredInstanceField("count", "J") == NULL);
+  EXPECT_TRUE(c->FindInstanceField("count", "J") == NULL);
+
+  // Wrong name.
+  EXPECT_TRUE(c->FindDeclaredInstanceField("Count", "I") == NULL);
+  EXPECT_TRUE(c->FindInstanceField("Count", "I") == NULL);
+
+  // Right name and type.
+  Field* f1 = c->FindDeclaredInstanceField("count", "I");
+  Field* f2 = c->FindInstanceField("count", "I");
+  EXPECT_TRUE(f1 != NULL);
+  EXPECT_TRUE(f2 != NULL);
+  EXPECT_EQ(f1, f2);
+
+  // TODO: check that s.count == 3.
+
+  // Ensure that we handle superclass fields correctly...
+  c = class_linker_->FindSystemClass("Ljava/lang/StringBuilder;");
+  ASSERT_TRUE(c != NULL);
+  // No StringBuilder.count...
+  EXPECT_TRUE(c->FindDeclaredInstanceField("count", "I") == NULL);
+  // ...but there is an AbstractStringBuilder.count.
+  EXPECT_TRUE(c->FindInstanceField("count", "I") != NULL);
+}
+
+TEST_F(ObjectTest, FindStaticField) {
+  String* s = String::AllocFromModifiedUtf8("ABC");
+  ASSERT_TRUE(s != NULL);
+  Class* c = s->GetClass();
+  ASSERT_TRUE(c != NULL);
+
+  // Wrong type.
+  EXPECT_TRUE(c->FindDeclaredStaticField("CASE_INSENSITIVE_ORDER", "I") == NULL);
+  EXPECT_TRUE(c->FindStaticField("CASE_INSENSITIVE_ORDER", "I") == NULL);
+
+  // Wrong name.
+  EXPECT_TRUE(c->FindDeclaredStaticField("cASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;") == NULL);
+  EXPECT_TRUE(c->FindStaticField("cASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;") == NULL);
+
+  // Right name and type.
+  Field* f1 = c->FindDeclaredStaticField("CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
+  Field* f2 = c->FindStaticField("CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
+  EXPECT_TRUE(f1 != NULL);
+  EXPECT_TRUE(f2 != NULL);
+  EXPECT_EQ(f1, f2);
+
+  // TODO: test static fields via superclasses.
+  // TODO: test static fields via interfaces.
+  // TODO: test that interfaces trump superclasses.
+}
+
 }  // namespace art