Squashed 'third_party/ntcore_2016/' content from commit d8de5e4

Change-Id: Id4839f41b6a620d8bae58dcf1710016671cc4992
git-subtree-dir: third_party/ntcore_2016
git-subtree-split: d8de5e4f19e612e7102172c0dbf152ce82d3d63a
diff --git a/java/arm-linux/LICENSE b/java/arm-linux/LICENSE
new file mode 100644
index 0000000..b40a0f4
--- /dev/null
+++ b/java/arm-linux/LICENSE
@@ -0,0 +1,347 @@
+The GNU General Public License (GPL)
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it.  By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users.  This General Public License applies to
+most of the Free Software Foundation's software and to any other program whose
+authors commit to using it.  (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price.  Our
+General Public Licenses are designed to make sure that you have the freedom to
+distribute copies of free software (and charge for this service if you wish),
+that you receive source code or can get it if you want it, that you can change
+the software or use pieces of it in new free programs; and that you know you
+can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights.  These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for
+a fee, you must give the recipients all the rights that you have.  You must
+make sure that they, too, receive or can get the source code.  And you must
+show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software.  If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents.  We
+wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program proprietary.
+To prevent this, we have made it clear that any patent must be licensed for
+everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License.  The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or
+translated into another language.  (Hereinafter, translation is included
+without limitation in the term "modification".) Each licensee is addressed as
+"you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope.  The act of running the Program is
+not restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program).  Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this License
+and to the absence of any warranty; and give any other recipients of the
+Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may
+at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all of
+these conditions:
+
+    a) You must cause the modified files to carry prominent notices stating
+    that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in whole or
+    in part contains or is derived from the Program or any part thereof, to be
+    licensed as a whole at no charge to all third parties under the terms of
+    this License.
+
+    c) If the modified program normally reads commands interactively when run,
+    you must cause it, when started running for such interactive use in the
+    most ordinary way, to print or display an announcement including an
+    appropriate copyright notice and a notice that there is no warranty (or
+    else, saying that you provide a warranty) and that users may redistribute
+    the program under these conditions, and telling the user how to view a copy
+    of this License.  (Exception: if the Program itself is interactive but does
+    not normally print such an announcement, your work based on the Program is
+    not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License, and
+its terms, do not apply to those sections when you distribute them as separate
+works.  But when you distribute the same sections as part of a whole which is a
+work based on the Program, the distribution of the whole must be on the terms
+of this License, whose permissions for other licensees extend to the entire
+whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise the
+right to control the distribution of derivative or collective works based on
+the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and
+2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable source
+    code, which must be distributed under the terms of Sections 1 and 2 above
+    on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three years, to
+    give any third party, for a charge no more than your cost of physically
+    performing source distribution, a complete machine-readable copy of the
+    corresponding source code, to be distributed under the terms of Sections 1
+    and 2 above on a medium customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer to
+    distribute corresponding source code.  (This alternative is allowed only
+    for noncommercial distribution and only if you received the program in
+    object code or executable form with such an offer, in accord with
+    Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it.  For an executable work, complete source code means all
+the source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and installation
+of the executable.  However, as a special exception, the source code
+distributed need not include anything that is normally distributed (in either
+source or binary form) with the major components (compiler, kernel, and so on)
+of the operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License.  Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License.  However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so
+long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works.  These actions are prohibited by law if you do not
+accept this License.  Therefore, by modifying or distributing the Program (or
+any work based on the Program), you indicate your acceptance of this License to
+do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to
+copy, distribute or modify the Program subject to these terms and conditions.
+You may not impose any further restrictions on the recipients' exercise of the
+rights granted herein.  You are not responsible for enforcing compliance by
+third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues), conditions
+are imposed on you (whether by court order, agreement or otherwise) that
+contradict the conditions of this License, they do not excuse you from the
+conditions of this License.  If you cannot distribute so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not distribute the Program at all.
+For example, if a patent license would not permit royalty-free redistribution
+of the Program by all those who receive copies directly or indirectly through
+you, then the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or
+other property right claims or to contest validity of any such claims; this
+section has the sole purpose of protecting the integrity of the free software
+distribution system, which is implemented by public license practices.  Many
+people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose that
+choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original
+copyright holder who places the Program under this License may add an explicit
+geographical distribution limitation excluding those countries, so that
+distribution is permitted only in or among countries not thus excluded.  In
+such case, this License incorporates the limitation as if written in the body
+of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time.  Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems
+or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any later
+version", you have the option of following the terms and conditions either of
+that version or of any later version published by the Free Software Foundation.
+If the Program does not specify a version number of this License, you may
+choose any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission.  For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of
+all derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
+PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
+PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
+INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
+BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
+OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program.  It is safest to attach
+them to the start of each source file to most effectively convey the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+    One line to give the program's name and a brief idea of what it does.
+
+    Copyright (C) <year> <name of author>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the Free
+    Software Foundation; either version 2 of the License, or (at your option)
+    any later version.
+
+    This program is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+    more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc., 59
+    Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when it
+starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
+    with ABSOLUTELY NO WARRANTY; for details type 'show w'.  This is free
+    software, and you are welcome to redistribute it under certain conditions;
+    type 'show c' for details.
+
+The hypothetical commands 'show w' and 'show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may be
+called something other than 'show w' and 'show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.  Here
+is a sample; alter the names:
+
+    Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+    'Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+    signature of Ty Coon, 1 April 1989
+
+    Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General Public
+License instead of this License.
+
+
+"CLASSPATH" EXCEPTION TO THE GPL
+
+Certain source files distributed by Oracle America and/or its affiliates are
+subject to the following clarification and special exception to the GPL, but
+only where Oracle has expressly included in the particular source file's header
+the words "Oracle designates this particular file as subject to the "Classpath"
+exception as provided by Oracle in the LICENSE file that accompanied this code."
+
+    Linking this library statically or dynamically with other modules is making
+    a combined work based on this library.  Thus, the terms and conditions of
+    the GNU General Public License cover the whole combination.
+
+    As a special exception, the copyright holders of this library give you
+    permission to link this library with independent modules to produce an
+    executable, regardless of the license terms of these independent modules,
+    and to copy and distribute the resulting executable under terms of your
+    choice, provided that you also meet, for each linked independent module,
+    the terms and conditions of the license of that module.  An independent
+    module is a module which is not derived from or based on this library.  If
+    you modify this library, you may extend this exception to your version of
+    the library, but you are not obligated to do so.  If you do not wish to do
+    so, delete this exception statement from your version.
diff --git a/java/arm-linux/jni.h b/java/arm-linux/jni.h
new file mode 100644
index 0000000..2e83cb7
--- /dev/null
+++ b/java/arm-linux/jni.h
@@ -0,0 +1,1960 @@
+/*
+ * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * We used part of Netscape's Java Runtime Interface (JRI) as the starting
+ * point of our design and implementation.
+ */
+
+/******************************************************************************
+ * Java Runtime Interface
+ * Copyright (c) 1996 Netscape Communications Corporation. All rights reserved.
+ *****************************************************************************/
+
+#ifndef _JAVASOFT_JNI_H_
+#define _JAVASOFT_JNI_H_
+
+#include <stdio.h>
+#include <stdarg.h>
+
+/* jni_md.h contains the machine-dependent typedefs for jbyte, jint
+   and jlong */
+
+#include "jni_md.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * JNI Types
+ */
+
+#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H
+
+typedef unsigned char   jboolean;
+typedef unsigned short  jchar;
+typedef short           jshort;
+typedef float           jfloat;
+typedef double          jdouble;
+
+typedef jint            jsize;
+
+#ifdef __cplusplus
+
+class _jobject {};
+class _jclass : public _jobject {};
+class _jthrowable : public _jobject {};
+class _jstring : public _jobject {};
+class _jarray : public _jobject {};
+class _jbooleanArray : public _jarray {};
+class _jbyteArray : public _jarray {};
+class _jcharArray : public _jarray {};
+class _jshortArray : public _jarray {};
+class _jintArray : public _jarray {};
+class _jlongArray : public _jarray {};
+class _jfloatArray : public _jarray {};
+class _jdoubleArray : public _jarray {};
+class _jobjectArray : public _jarray {};
+
+typedef _jobject *jobject;
+typedef _jclass *jclass;
+typedef _jthrowable *jthrowable;
+typedef _jstring *jstring;
+typedef _jarray *jarray;
+typedef _jbooleanArray *jbooleanArray;
+typedef _jbyteArray *jbyteArray;
+typedef _jcharArray *jcharArray;
+typedef _jshortArray *jshortArray;
+typedef _jintArray *jintArray;
+typedef _jlongArray *jlongArray;
+typedef _jfloatArray *jfloatArray;
+typedef _jdoubleArray *jdoubleArray;
+typedef _jobjectArray *jobjectArray;
+
+#else
+
+struct _jobject;
+
+typedef struct _jobject *jobject;
+typedef jobject jclass;
+typedef jobject jthrowable;
+typedef jobject jstring;
+typedef jobject jarray;
+typedef jarray jbooleanArray;
+typedef jarray jbyteArray;
+typedef jarray jcharArray;
+typedef jarray jshortArray;
+typedef jarray jintArray;
+typedef jarray jlongArray;
+typedef jarray jfloatArray;
+typedef jarray jdoubleArray;
+typedef jarray jobjectArray;
+
+#endif
+
+typedef jobject jweak;
+
+typedef union jvalue {
+    jboolean z;
+    jbyte    b;
+    jchar    c;
+    jshort   s;
+    jint     i;
+    jlong    j;
+    jfloat   f;
+    jdouble  d;
+    jobject  l;
+} jvalue;
+
+struct _jfieldID;
+typedef struct _jfieldID *jfieldID;
+
+struct _jmethodID;
+typedef struct _jmethodID *jmethodID;
+
+/* Return values from jobjectRefType */
+typedef enum _jobjectType {
+     JNIInvalidRefType    = 0,
+     JNILocalRefType      = 1,
+     JNIGlobalRefType     = 2,
+     JNIWeakGlobalRefType = 3
+} jobjectRefType;
+
+
+#endif /* JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H */
+
+/*
+ * jboolean constants
+ */
+
+#define JNI_FALSE 0
+#define JNI_TRUE 1
+
+/*
+ * possible return values for JNI functions.
+ */
+
+#define JNI_OK           0                 /* success */
+#define JNI_ERR          (-1)              /* unknown error */
+#define JNI_EDETACHED    (-2)              /* thread detached from the VM */
+#define JNI_EVERSION     (-3)              /* JNI version error */
+#define JNI_ENOMEM       (-4)              /* not enough memory */
+#define JNI_EEXIST       (-5)              /* VM already created */
+#define JNI_EINVAL       (-6)              /* invalid arguments */
+
+/*
+ * used in ReleaseScalarArrayElements
+ */
+
+#define JNI_COMMIT 1
+#define JNI_ABORT 2
+
+/*
+ * used in RegisterNatives to describe native method name, signature,
+ * and function pointer.
+ */
+
+typedef struct {
+    char *name;
+    char *signature;
+    void *fnPtr;
+} JNINativeMethod;
+
+/*
+ * JNI Native Method Interface.
+ */
+
+struct JNINativeInterface_;
+
+struct JNIEnv_;
+
+#ifdef __cplusplus
+typedef JNIEnv_ JNIEnv;
+#else
+typedef const struct JNINativeInterface_ *JNIEnv;
+#endif
+
+/*
+ * JNI Invocation Interface.
+ */
+
+struct JNIInvokeInterface_;
+
+struct JavaVM_;
+
+#ifdef __cplusplus
+typedef JavaVM_ JavaVM;
+#else
+typedef const struct JNIInvokeInterface_ *JavaVM;
+#endif
+
+struct JNINativeInterface_ {
+    void *reserved0;
+    void *reserved1;
+    void *reserved2;
+
+    void *reserved3;
+    jint (JNICALL *GetVersion)(JNIEnv *env);
+
+    jclass (JNICALL *DefineClass)
+      (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
+       jsize len);
+    jclass (JNICALL *FindClass)
+      (JNIEnv *env, const char *name);
+
+    jmethodID (JNICALL *FromReflectedMethod)
+      (JNIEnv *env, jobject method);
+    jfieldID (JNICALL *FromReflectedField)
+      (JNIEnv *env, jobject field);
+
+    jobject (JNICALL *ToReflectedMethod)
+      (JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);
+
+    jclass (JNICALL *GetSuperclass)
+      (JNIEnv *env, jclass sub);
+    jboolean (JNICALL *IsAssignableFrom)
+      (JNIEnv *env, jclass sub, jclass sup);
+
+    jobject (JNICALL *ToReflectedField)
+      (JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);
+
+    jint (JNICALL *Throw)
+      (JNIEnv *env, jthrowable obj);
+    jint (JNICALL *ThrowNew)
+      (JNIEnv *env, jclass clazz, const char *msg);
+    jthrowable (JNICALL *ExceptionOccurred)
+      (JNIEnv *env);
+    void (JNICALL *ExceptionDescribe)
+      (JNIEnv *env);
+    void (JNICALL *ExceptionClear)
+      (JNIEnv *env);
+    void (JNICALL *FatalError)
+      (JNIEnv *env, const char *msg);
+
+    jint (JNICALL *PushLocalFrame)
+      (JNIEnv *env, jint capacity);
+    jobject (JNICALL *PopLocalFrame)
+      (JNIEnv *env, jobject result);
+
+    jobject (JNICALL *NewGlobalRef)
+      (JNIEnv *env, jobject lobj);
+    void (JNICALL *DeleteGlobalRef)
+      (JNIEnv *env, jobject gref);
+    void (JNICALL *DeleteLocalRef)
+      (JNIEnv *env, jobject obj);
+    jboolean (JNICALL *IsSameObject)
+      (JNIEnv *env, jobject obj1, jobject obj2);
+    jobject (JNICALL *NewLocalRef)
+      (JNIEnv *env, jobject ref);
+    jint (JNICALL *EnsureLocalCapacity)
+      (JNIEnv *env, jint capacity);
+
+    jobject (JNICALL *AllocObject)
+      (JNIEnv *env, jclass clazz);
+    jobject (JNICALL *NewObject)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jobject (JNICALL *NewObjectV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jobject (JNICALL *NewObjectA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jclass (JNICALL *GetObjectClass)
+      (JNIEnv *env, jobject obj);
+    jboolean (JNICALL *IsInstanceOf)
+      (JNIEnv *env, jobject obj, jclass clazz);
+
+    jmethodID (JNICALL *GetMethodID)
+      (JNIEnv *env, jclass clazz, const char *name, const char *sig);
+
+    jobject (JNICALL *CallObjectMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jobject (JNICALL *CallObjectMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jobject (JNICALL *CallObjectMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args);
+
+    jboolean (JNICALL *CallBooleanMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jboolean (JNICALL *CallBooleanMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jboolean (JNICALL *CallBooleanMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args);
+
+    jbyte (JNICALL *CallByteMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jbyte (JNICALL *CallByteMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jbyte (JNICALL *CallByteMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jchar (JNICALL *CallCharMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jchar (JNICALL *CallCharMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jchar (JNICALL *CallCharMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jshort (JNICALL *CallShortMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jshort (JNICALL *CallShortMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jshort (JNICALL *CallShortMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jint (JNICALL *CallIntMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jint (JNICALL *CallIntMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jint (JNICALL *CallIntMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jlong (JNICALL *CallLongMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jlong (JNICALL *CallLongMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jlong (JNICALL *CallLongMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jfloat (JNICALL *CallFloatMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jfloat (JNICALL *CallFloatMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jfloat (JNICALL *CallFloatMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jdouble (JNICALL *CallDoubleMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jdouble (JNICALL *CallDoubleMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    jdouble (JNICALL *CallDoubleMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    void (JNICALL *CallVoidMethod)
+      (JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    void (JNICALL *CallVoidMethodV)
+      (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
+    void (JNICALL *CallVoidMethodA)
+      (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args);
+
+    jobject (JNICALL *CallNonvirtualObjectMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jobject (JNICALL *CallNonvirtualObjectMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jobject (JNICALL *CallNonvirtualObjectMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue * args);
+
+    jboolean (JNICALL *CallNonvirtualBooleanMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jboolean (JNICALL *CallNonvirtualBooleanMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jboolean (JNICALL *CallNonvirtualBooleanMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue * args);
+
+    jbyte (JNICALL *CallNonvirtualByteMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jbyte (JNICALL *CallNonvirtualByteMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jbyte (JNICALL *CallNonvirtualByteMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    jchar (JNICALL *CallNonvirtualCharMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jchar (JNICALL *CallNonvirtualCharMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jchar (JNICALL *CallNonvirtualCharMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    jshort (JNICALL *CallNonvirtualShortMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jshort (JNICALL *CallNonvirtualShortMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jshort (JNICALL *CallNonvirtualShortMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    jint (JNICALL *CallNonvirtualIntMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jint (JNICALL *CallNonvirtualIntMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jint (JNICALL *CallNonvirtualIntMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    jlong (JNICALL *CallNonvirtualLongMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jlong (JNICALL *CallNonvirtualLongMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jlong (JNICALL *CallNonvirtualLongMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    jfloat (JNICALL *CallNonvirtualFloatMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jfloat (JNICALL *CallNonvirtualFloatMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jfloat (JNICALL *CallNonvirtualFloatMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    jdouble (JNICALL *CallNonvirtualDoubleMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    jdouble (JNICALL *CallNonvirtualDoubleMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    jdouble (JNICALL *CallNonvirtualDoubleMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue *args);
+
+    void (JNICALL *CallNonvirtualVoidMethod)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
+    void (JNICALL *CallNonvirtualVoidMethodV)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       va_list args);
+    void (JNICALL *CallNonvirtualVoidMethodA)
+      (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
+       const jvalue * args);
+
+    jfieldID (JNICALL *GetFieldID)
+      (JNIEnv *env, jclass clazz, const char *name, const char *sig);
+
+    jobject (JNICALL *GetObjectField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jboolean (JNICALL *GetBooleanField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jbyte (JNICALL *GetByteField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jchar (JNICALL *GetCharField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jshort (JNICALL *GetShortField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jint (JNICALL *GetIntField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jlong (JNICALL *GetLongField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jfloat (JNICALL *GetFloatField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+    jdouble (JNICALL *GetDoubleField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID);
+
+    void (JNICALL *SetObjectField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jobject val);
+    void (JNICALL *SetBooleanField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jboolean val);
+    void (JNICALL *SetByteField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jbyte val);
+    void (JNICALL *SetCharField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jchar val);
+    void (JNICALL *SetShortField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jshort val);
+    void (JNICALL *SetIntField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jint val);
+    void (JNICALL *SetLongField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jlong val);
+    void (JNICALL *SetFloatField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jfloat val);
+    void (JNICALL *SetDoubleField)
+      (JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val);
+
+    jmethodID (JNICALL *GetStaticMethodID)
+      (JNIEnv *env, jclass clazz, const char *name, const char *sig);
+
+    jobject (JNICALL *CallStaticObjectMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jobject (JNICALL *CallStaticObjectMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jobject (JNICALL *CallStaticObjectMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jboolean (JNICALL *CallStaticBooleanMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jboolean (JNICALL *CallStaticBooleanMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jboolean (JNICALL *CallStaticBooleanMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jbyte (JNICALL *CallStaticByteMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jbyte (JNICALL *CallStaticByteMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jbyte (JNICALL *CallStaticByteMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jchar (JNICALL *CallStaticCharMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jchar (JNICALL *CallStaticCharMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jchar (JNICALL *CallStaticCharMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jshort (JNICALL *CallStaticShortMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jshort (JNICALL *CallStaticShortMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jshort (JNICALL *CallStaticShortMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jint (JNICALL *CallStaticIntMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jint (JNICALL *CallStaticIntMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jint (JNICALL *CallStaticIntMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jlong (JNICALL *CallStaticLongMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jlong (JNICALL *CallStaticLongMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jlong (JNICALL *CallStaticLongMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jfloat (JNICALL *CallStaticFloatMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jfloat (JNICALL *CallStaticFloatMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jfloat (JNICALL *CallStaticFloatMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jdouble (JNICALL *CallStaticDoubleMethod)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jdouble (JNICALL *CallStaticDoubleMethodV)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
+    jdouble (JNICALL *CallStaticDoubleMethodA)
+      (JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    void (JNICALL *CallStaticVoidMethod)
+      (JNIEnv *env, jclass cls, jmethodID methodID, ...);
+    void (JNICALL *CallStaticVoidMethodV)
+      (JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
+    void (JNICALL *CallStaticVoidMethodA)
+      (JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args);
+
+    jfieldID (JNICALL *GetStaticFieldID)
+      (JNIEnv *env, jclass clazz, const char *name, const char *sig);
+    jobject (JNICALL *GetStaticObjectField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jboolean (JNICALL *GetStaticBooleanField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jbyte (JNICALL *GetStaticByteField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jchar (JNICALL *GetStaticCharField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jshort (JNICALL *GetStaticShortField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jint (JNICALL *GetStaticIntField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jlong (JNICALL *GetStaticLongField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jfloat (JNICALL *GetStaticFloatField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jdouble (JNICALL *GetStaticDoubleField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID);
+
+    void (JNICALL *SetStaticObjectField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jobject value);
+    void (JNICALL *SetStaticBooleanField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jboolean value);
+    void (JNICALL *SetStaticByteField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jbyte value);
+    void (JNICALL *SetStaticCharField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jchar value);
+    void (JNICALL *SetStaticShortField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jshort value);
+    void (JNICALL *SetStaticIntField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jint value);
+    void (JNICALL *SetStaticLongField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jlong value);
+    void (JNICALL *SetStaticFloatField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jfloat value);
+    void (JNICALL *SetStaticDoubleField)
+      (JNIEnv *env, jclass clazz, jfieldID fieldID, jdouble value);
+
+    jstring (JNICALL *NewString)
+      (JNIEnv *env, const jchar *unicode, jsize len);
+    jsize (JNICALL *GetStringLength)
+      (JNIEnv *env, jstring str);
+    const jchar *(JNICALL *GetStringChars)
+      (JNIEnv *env, jstring str, jboolean *isCopy);
+    void (JNICALL *ReleaseStringChars)
+      (JNIEnv *env, jstring str, const jchar *chars);
+
+    jstring (JNICALL *NewStringUTF)
+      (JNIEnv *env, const char *utf);
+    jsize (JNICALL *GetStringUTFLength)
+      (JNIEnv *env, jstring str);
+    const char* (JNICALL *GetStringUTFChars)
+      (JNIEnv *env, jstring str, jboolean *isCopy);
+    void (JNICALL *ReleaseStringUTFChars)
+      (JNIEnv *env, jstring str, const char* chars);
+
+
+    jsize (JNICALL *GetArrayLength)
+      (JNIEnv *env, jarray array);
+
+    jobjectArray (JNICALL *NewObjectArray)
+      (JNIEnv *env, jsize len, jclass clazz, jobject init);
+    jobject (JNICALL *GetObjectArrayElement)
+      (JNIEnv *env, jobjectArray array, jsize index);
+    void (JNICALL *SetObjectArrayElement)
+      (JNIEnv *env, jobjectArray array, jsize index, jobject val);
+
+    jbooleanArray (JNICALL *NewBooleanArray)
+      (JNIEnv *env, jsize len);
+    jbyteArray (JNICALL *NewByteArray)
+      (JNIEnv *env, jsize len);
+    jcharArray (JNICALL *NewCharArray)
+      (JNIEnv *env, jsize len);
+    jshortArray (JNICALL *NewShortArray)
+      (JNIEnv *env, jsize len);
+    jintArray (JNICALL *NewIntArray)
+      (JNIEnv *env, jsize len);
+    jlongArray (JNICALL *NewLongArray)
+      (JNIEnv *env, jsize len);
+    jfloatArray (JNICALL *NewFloatArray)
+      (JNIEnv *env, jsize len);
+    jdoubleArray (JNICALL *NewDoubleArray)
+      (JNIEnv *env, jsize len);
+
+    jboolean * (JNICALL *GetBooleanArrayElements)
+      (JNIEnv *env, jbooleanArray array, jboolean *isCopy);
+    jbyte * (JNICALL *GetByteArrayElements)
+      (JNIEnv *env, jbyteArray array, jboolean *isCopy);
+    jchar * (JNICALL *GetCharArrayElements)
+      (JNIEnv *env, jcharArray array, jboolean *isCopy);
+    jshort * (JNICALL *GetShortArrayElements)
+      (JNIEnv *env, jshortArray array, jboolean *isCopy);
+    jint * (JNICALL *GetIntArrayElements)
+      (JNIEnv *env, jintArray array, jboolean *isCopy);
+    jlong * (JNICALL *GetLongArrayElements)
+      (JNIEnv *env, jlongArray array, jboolean *isCopy);
+    jfloat * (JNICALL *GetFloatArrayElements)
+      (JNIEnv *env, jfloatArray array, jboolean *isCopy);
+    jdouble * (JNICALL *GetDoubleArrayElements)
+      (JNIEnv *env, jdoubleArray array, jboolean *isCopy);
+
+    void (JNICALL *ReleaseBooleanArrayElements)
+      (JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode);
+    void (JNICALL *ReleaseByteArrayElements)
+      (JNIEnv *env, jbyteArray array, jbyte *elems, jint mode);
+    void (JNICALL *ReleaseCharArrayElements)
+      (JNIEnv *env, jcharArray array, jchar *elems, jint mode);
+    void (JNICALL *ReleaseShortArrayElements)
+      (JNIEnv *env, jshortArray array, jshort *elems, jint mode);
+    void (JNICALL *ReleaseIntArrayElements)
+      (JNIEnv *env, jintArray array, jint *elems, jint mode);
+    void (JNICALL *ReleaseLongArrayElements)
+      (JNIEnv *env, jlongArray array, jlong *elems, jint mode);
+    void (JNICALL *ReleaseFloatArrayElements)
+      (JNIEnv *env, jfloatArray array, jfloat *elems, jint mode);
+    void (JNICALL *ReleaseDoubleArrayElements)
+      (JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode);
+
+    void (JNICALL *GetBooleanArrayRegion)
+      (JNIEnv *env, jbooleanArray array, jsize start, jsize l, jboolean *buf);
+    void (JNICALL *GetByteArrayRegion)
+      (JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf);
+    void (JNICALL *GetCharArrayRegion)
+      (JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf);
+    void (JNICALL *GetShortArrayRegion)
+      (JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf);
+    void (JNICALL *GetIntArrayRegion)
+      (JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf);
+    void (JNICALL *GetLongArrayRegion)
+      (JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf);
+    void (JNICALL *GetFloatArrayRegion)
+      (JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf);
+    void (JNICALL *GetDoubleArrayRegion)
+      (JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf);
+
+    void (JNICALL *SetBooleanArrayRegion)
+      (JNIEnv *env, jbooleanArray array, jsize start, jsize l, const jboolean *buf);
+    void (JNICALL *SetByteArrayRegion)
+      (JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte *buf);
+    void (JNICALL *SetCharArrayRegion)
+      (JNIEnv *env, jcharArray array, jsize start, jsize len, const jchar *buf);
+    void (JNICALL *SetShortArrayRegion)
+      (JNIEnv *env, jshortArray array, jsize start, jsize len, const jshort *buf);
+    void (JNICALL *SetIntArrayRegion)
+      (JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf);
+    void (JNICALL *SetLongArrayRegion)
+      (JNIEnv *env, jlongArray array, jsize start, jsize len, const jlong *buf);
+    void (JNICALL *SetFloatArrayRegion)
+      (JNIEnv *env, jfloatArray array, jsize start, jsize len, const jfloat *buf);
+    void (JNICALL *SetDoubleArrayRegion)
+      (JNIEnv *env, jdoubleArray array, jsize start, jsize len, const jdouble *buf);
+
+    jint (JNICALL *RegisterNatives)
+      (JNIEnv *env, jclass clazz, const JNINativeMethod *methods,
+       jint nMethods);
+    jint (JNICALL *UnregisterNatives)
+      (JNIEnv *env, jclass clazz);
+
+    jint (JNICALL *MonitorEnter)
+      (JNIEnv *env, jobject obj);
+    jint (JNICALL *MonitorExit)
+      (JNIEnv *env, jobject obj);
+
+    jint (JNICALL *GetJavaVM)
+      (JNIEnv *env, JavaVM **vm);
+
+    void (JNICALL *GetStringRegion)
+      (JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
+    void (JNICALL *GetStringUTFRegion)
+      (JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
+
+    void * (JNICALL *GetPrimitiveArrayCritical)
+      (JNIEnv *env, jarray array, jboolean *isCopy);
+    void (JNICALL *ReleasePrimitiveArrayCritical)
+      (JNIEnv *env, jarray array, void *carray, jint mode);
+
+    const jchar * (JNICALL *GetStringCritical)
+      (JNIEnv *env, jstring string, jboolean *isCopy);
+    void (JNICALL *ReleaseStringCritical)
+      (JNIEnv *env, jstring string, const jchar *cstring);
+
+    jweak (JNICALL *NewWeakGlobalRef)
+       (JNIEnv *env, jobject obj);
+    void (JNICALL *DeleteWeakGlobalRef)
+       (JNIEnv *env, jweak ref);
+
+    jboolean (JNICALL *ExceptionCheck)
+       (JNIEnv *env);
+
+    jobject (JNICALL *NewDirectByteBuffer)
+       (JNIEnv* env, void* address, jlong capacity);
+    void* (JNICALL *GetDirectBufferAddress)
+       (JNIEnv* env, jobject buf);
+    jlong (JNICALL *GetDirectBufferCapacity)
+       (JNIEnv* env, jobject buf);
+
+    /* New JNI 1.6 Features */
+
+    jobjectRefType (JNICALL *GetObjectRefType)
+        (JNIEnv* env, jobject obj);
+};
+
+/*
+ * We use inlined functions for C++ so that programmers can write:
+ *
+ *    env->FindClass("java/lang/String")
+ *
+ * in C++ rather than:
+ *
+ *    (*env)->FindClass(env, "java/lang/String")
+ *
+ * in C.
+ */
+
+struct JNIEnv_ {
+    const struct JNINativeInterface_ *functions;
+#ifdef __cplusplus
+
+    jint GetVersion() {
+        return functions->GetVersion(this);
+    }
+    jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
+                       jsize len) {
+        return functions->DefineClass(this, name, loader, buf, len);
+    }
+    jclass FindClass(const char *name) {
+        return functions->FindClass(this, name);
+    }
+    jmethodID FromReflectedMethod(jobject method) {
+        return functions->FromReflectedMethod(this,method);
+    }
+    jfieldID FromReflectedField(jobject field) {
+        return functions->FromReflectedField(this,field);
+    }
+
+    jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) {
+        return functions->ToReflectedMethod(this, cls, methodID, isStatic);
+    }
+
+    jclass GetSuperclass(jclass sub) {
+        return functions->GetSuperclass(this, sub);
+    }
+    jboolean IsAssignableFrom(jclass sub, jclass sup) {
+        return functions->IsAssignableFrom(this, sub, sup);
+    }
+
+    jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) {
+        return functions->ToReflectedField(this,cls,fieldID,isStatic);
+    }
+
+    jint Throw(jthrowable obj) {
+        return functions->Throw(this, obj);
+    }
+    jint ThrowNew(jclass clazz, const char *msg) {
+        return functions->ThrowNew(this, clazz, msg);
+    }
+    jthrowable ExceptionOccurred() {
+        return functions->ExceptionOccurred(this);
+    }
+    void ExceptionDescribe() {
+        functions->ExceptionDescribe(this);
+    }
+    void ExceptionClear() {
+        functions->ExceptionClear(this);
+    }
+    void FatalError(const char *msg) {
+        functions->FatalError(this, msg);
+    }
+
+    jint PushLocalFrame(jint capacity) {
+        return functions->PushLocalFrame(this,capacity);
+    }
+    jobject PopLocalFrame(jobject result) {
+        return functions->PopLocalFrame(this,result);
+    }
+
+    jobject NewGlobalRef(jobject lobj) {
+        return functions->NewGlobalRef(this,lobj);
+    }
+    void DeleteGlobalRef(jobject gref) {
+        functions->DeleteGlobalRef(this,gref);
+    }
+    void DeleteLocalRef(jobject obj) {
+        functions->DeleteLocalRef(this, obj);
+    }
+
+    jboolean IsSameObject(jobject obj1, jobject obj2) {
+        return functions->IsSameObject(this,obj1,obj2);
+    }
+
+    jobject NewLocalRef(jobject ref) {
+        return functions->NewLocalRef(this,ref);
+    }
+    jint EnsureLocalCapacity(jint capacity) {
+        return functions->EnsureLocalCapacity(this,capacity);
+    }
+
+    jobject AllocObject(jclass clazz) {
+        return functions->AllocObject(this,clazz);
+    }
+    jobject NewObject(jclass clazz, jmethodID methodID, ...) {
+        va_list args;
+        jobject result;
+        va_start(args, methodID);
+        result = functions->NewObjectV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jobject NewObjectV(jclass clazz, jmethodID methodID,
+                       va_list args) {
+        return functions->NewObjectV(this,clazz,methodID,args);
+    }
+    jobject NewObjectA(jclass clazz, jmethodID methodID,
+                       const jvalue *args) {
+        return functions->NewObjectA(this,clazz,methodID,args);
+    }
+
+    jclass GetObjectClass(jobject obj) {
+        return functions->GetObjectClass(this,obj);
+    }
+    jboolean IsInstanceOf(jobject obj, jclass clazz) {
+        return functions->IsInstanceOf(this,obj,clazz);
+    }
+
+    jmethodID GetMethodID(jclass clazz, const char *name,
+                          const char *sig) {
+        return functions->GetMethodID(this,clazz,name,sig);
+    }
+
+    jobject CallObjectMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jobject result;
+        va_start(args,methodID);
+        result = functions->CallObjectMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jobject CallObjectMethodV(jobject obj, jmethodID methodID,
+                        va_list args) {
+        return functions->CallObjectMethodV(this,obj,methodID,args);
+    }
+    jobject CallObjectMethodA(jobject obj, jmethodID methodID,
+                        const jvalue * args) {
+        return functions->CallObjectMethodA(this,obj,methodID,args);
+    }
+
+    jboolean CallBooleanMethod(jobject obj,
+                               jmethodID methodID, ...) {
+        va_list args;
+        jboolean result;
+        va_start(args,methodID);
+        result = functions->CallBooleanMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jboolean CallBooleanMethodV(jobject obj, jmethodID methodID,
+                                va_list args) {
+        return functions->CallBooleanMethodV(this,obj,methodID,args);
+    }
+    jboolean CallBooleanMethodA(jobject obj, jmethodID methodID,
+                                const jvalue * args) {
+        return functions->CallBooleanMethodA(this,obj,methodID, args);
+    }
+
+    jbyte CallByteMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jbyte result;
+        va_start(args,methodID);
+        result = functions->CallByteMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jbyte CallByteMethodV(jobject obj, jmethodID methodID,
+                          va_list args) {
+        return functions->CallByteMethodV(this,obj,methodID,args);
+    }
+    jbyte CallByteMethodA(jobject obj, jmethodID methodID,
+                          const jvalue * args) {
+        return functions->CallByteMethodA(this,obj,methodID,args);
+    }
+
+    jchar CallCharMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jchar result;
+        va_start(args,methodID);
+        result = functions->CallCharMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jchar CallCharMethodV(jobject obj, jmethodID methodID,
+                          va_list args) {
+        return functions->CallCharMethodV(this,obj,methodID,args);
+    }
+    jchar CallCharMethodA(jobject obj, jmethodID methodID,
+                          const jvalue * args) {
+        return functions->CallCharMethodA(this,obj,methodID,args);
+    }
+
+    jshort CallShortMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jshort result;
+        va_start(args,methodID);
+        result = functions->CallShortMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jshort CallShortMethodV(jobject obj, jmethodID methodID,
+                            va_list args) {
+        return functions->CallShortMethodV(this,obj,methodID,args);
+    }
+    jshort CallShortMethodA(jobject obj, jmethodID methodID,
+                            const jvalue * args) {
+        return functions->CallShortMethodA(this,obj,methodID,args);
+    }
+
+    jint CallIntMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jint result;
+        va_start(args,methodID);
+        result = functions->CallIntMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jint CallIntMethodV(jobject obj, jmethodID methodID,
+                        va_list args) {
+        return functions->CallIntMethodV(this,obj,methodID,args);
+    }
+    jint CallIntMethodA(jobject obj, jmethodID methodID,
+                        const jvalue * args) {
+        return functions->CallIntMethodA(this,obj,methodID,args);
+    }
+
+    jlong CallLongMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jlong result;
+        va_start(args,methodID);
+        result = functions->CallLongMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jlong CallLongMethodV(jobject obj, jmethodID methodID,
+                          va_list args) {
+        return functions->CallLongMethodV(this,obj,methodID,args);
+    }
+    jlong CallLongMethodA(jobject obj, jmethodID methodID,
+                          const jvalue * args) {
+        return functions->CallLongMethodA(this,obj,methodID,args);
+    }
+
+    jfloat CallFloatMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jfloat result;
+        va_start(args,methodID);
+        result = functions->CallFloatMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jfloat CallFloatMethodV(jobject obj, jmethodID methodID,
+                            va_list args) {
+        return functions->CallFloatMethodV(this,obj,methodID,args);
+    }
+    jfloat CallFloatMethodA(jobject obj, jmethodID methodID,
+                            const jvalue * args) {
+        return functions->CallFloatMethodA(this,obj,methodID,args);
+    }
+
+    jdouble CallDoubleMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        jdouble result;
+        va_start(args,methodID);
+        result = functions->CallDoubleMethodV(this,obj,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jdouble CallDoubleMethodV(jobject obj, jmethodID methodID,
+                        va_list args) {
+        return functions->CallDoubleMethodV(this,obj,methodID,args);
+    }
+    jdouble CallDoubleMethodA(jobject obj, jmethodID methodID,
+                        const jvalue * args) {
+        return functions->CallDoubleMethodA(this,obj,methodID,args);
+    }
+
+    void CallVoidMethod(jobject obj, jmethodID methodID, ...) {
+        va_list args;
+        va_start(args,methodID);
+        functions->CallVoidMethodV(this,obj,methodID,args);
+        va_end(args);
+    }
+    void CallVoidMethodV(jobject obj, jmethodID methodID,
+                         va_list args) {
+        functions->CallVoidMethodV(this,obj,methodID,args);
+    }
+    void CallVoidMethodA(jobject obj, jmethodID methodID,
+                         const jvalue * args) {
+        functions->CallVoidMethodA(this,obj,methodID,args);
+    }
+
+    jobject CallNonvirtualObjectMethod(jobject obj, jclass clazz,
+                                       jmethodID methodID, ...) {
+        va_list args;
+        jobject result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualObjectMethodV(this,obj,clazz,
+                                                        methodID,args);
+        va_end(args);
+        return result;
+    }
+    jobject CallNonvirtualObjectMethodV(jobject obj, jclass clazz,
+                                        jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualObjectMethodV(this,obj,clazz,
+                                                      methodID,args);
+    }
+    jobject CallNonvirtualObjectMethodA(jobject obj, jclass clazz,
+                                        jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualObjectMethodA(this,obj,clazz,
+                                                      methodID,args);
+    }
+
+    jboolean CallNonvirtualBooleanMethod(jobject obj, jclass clazz,
+                                         jmethodID methodID, ...) {
+        va_list args;
+        jboolean result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualBooleanMethodV(this,obj,clazz,
+                                                         methodID,args);
+        va_end(args);
+        return result;
+    }
+    jboolean CallNonvirtualBooleanMethodV(jobject obj, jclass clazz,
+                                          jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualBooleanMethodV(this,obj,clazz,
+                                                       methodID,args);
+    }
+    jboolean CallNonvirtualBooleanMethodA(jobject obj, jclass clazz,
+                                          jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualBooleanMethodA(this,obj,clazz,
+                                                       methodID, args);
+    }
+
+    jbyte CallNonvirtualByteMethod(jobject obj, jclass clazz,
+                                   jmethodID methodID, ...) {
+        va_list args;
+        jbyte result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualByteMethodV(this,obj,clazz,
+                                                      methodID,args);
+        va_end(args);
+        return result;
+    }
+    jbyte CallNonvirtualByteMethodV(jobject obj, jclass clazz,
+                                    jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualByteMethodV(this,obj,clazz,
+                                                    methodID,args);
+    }
+    jbyte CallNonvirtualByteMethodA(jobject obj, jclass clazz,
+                                    jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualByteMethodA(this,obj,clazz,
+                                                    methodID,args);
+    }
+
+    jchar CallNonvirtualCharMethod(jobject obj, jclass clazz,
+                                   jmethodID methodID, ...) {
+        va_list args;
+        jchar result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualCharMethodV(this,obj,clazz,
+                                                      methodID,args);
+        va_end(args);
+        return result;
+    }
+    jchar CallNonvirtualCharMethodV(jobject obj, jclass clazz,
+                                    jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualCharMethodV(this,obj,clazz,
+                                                    methodID,args);
+    }
+    jchar CallNonvirtualCharMethodA(jobject obj, jclass clazz,
+                                    jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualCharMethodA(this,obj,clazz,
+                                                    methodID,args);
+    }
+
+    jshort CallNonvirtualShortMethod(jobject obj, jclass clazz,
+                                     jmethodID methodID, ...) {
+        va_list args;
+        jshort result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualShortMethodV(this,obj,clazz,
+                                                       methodID,args);
+        va_end(args);
+        return result;
+    }
+    jshort CallNonvirtualShortMethodV(jobject obj, jclass clazz,
+                                      jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualShortMethodV(this,obj,clazz,
+                                                     methodID,args);
+    }
+    jshort CallNonvirtualShortMethodA(jobject obj, jclass clazz,
+                                      jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualShortMethodA(this,obj,clazz,
+                                                     methodID,args);
+    }
+
+    jint CallNonvirtualIntMethod(jobject obj, jclass clazz,
+                                 jmethodID methodID, ...) {
+        va_list args;
+        jint result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualIntMethodV(this,obj,clazz,
+                                                     methodID,args);
+        va_end(args);
+        return result;
+    }
+    jint CallNonvirtualIntMethodV(jobject obj, jclass clazz,
+                                  jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualIntMethodV(this,obj,clazz,
+                                                   methodID,args);
+    }
+    jint CallNonvirtualIntMethodA(jobject obj, jclass clazz,
+                                  jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualIntMethodA(this,obj,clazz,
+                                                   methodID,args);
+    }
+
+    jlong CallNonvirtualLongMethod(jobject obj, jclass clazz,
+                                   jmethodID methodID, ...) {
+        va_list args;
+        jlong result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualLongMethodV(this,obj,clazz,
+                                                      methodID,args);
+        va_end(args);
+        return result;
+    }
+    jlong CallNonvirtualLongMethodV(jobject obj, jclass clazz,
+                                    jmethodID methodID, va_list args) {
+        return functions->CallNonvirtualLongMethodV(this,obj,clazz,
+                                                    methodID,args);
+    }
+    jlong CallNonvirtualLongMethodA(jobject obj, jclass clazz,
+                                    jmethodID methodID, const jvalue * args) {
+        return functions->CallNonvirtualLongMethodA(this,obj,clazz,
+                                                    methodID,args);
+    }
+
+    jfloat CallNonvirtualFloatMethod(jobject obj, jclass clazz,
+                                     jmethodID methodID, ...) {
+        va_list args;
+        jfloat result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualFloatMethodV(this,obj,clazz,
+                                                       methodID,args);
+        va_end(args);
+        return result;
+    }
+    jfloat CallNonvirtualFloatMethodV(jobject obj, jclass clazz,
+                                      jmethodID methodID,
+                                      va_list args) {
+        return functions->CallNonvirtualFloatMethodV(this,obj,clazz,
+                                                     methodID,args);
+    }
+    jfloat CallNonvirtualFloatMethodA(jobject obj, jclass clazz,
+                                      jmethodID methodID,
+                                      const jvalue * args) {
+        return functions->CallNonvirtualFloatMethodA(this,obj,clazz,
+                                                     methodID,args);
+    }
+
+    jdouble CallNonvirtualDoubleMethod(jobject obj, jclass clazz,
+                                       jmethodID methodID, ...) {
+        va_list args;
+        jdouble result;
+        va_start(args,methodID);
+        result = functions->CallNonvirtualDoubleMethodV(this,obj,clazz,
+                                                        methodID,args);
+        va_end(args);
+        return result;
+    }
+    jdouble CallNonvirtualDoubleMethodV(jobject obj, jclass clazz,
+                                        jmethodID methodID,
+                                        va_list args) {
+        return functions->CallNonvirtualDoubleMethodV(this,obj,clazz,
+                                                      methodID,args);
+    }
+    jdouble CallNonvirtualDoubleMethodA(jobject obj, jclass clazz,
+                                        jmethodID methodID,
+                                        const jvalue * args) {
+        return functions->CallNonvirtualDoubleMethodA(this,obj,clazz,
+                                                      methodID,args);
+    }
+
+    void CallNonvirtualVoidMethod(jobject obj, jclass clazz,
+                                  jmethodID methodID, ...) {
+        va_list args;
+        va_start(args,methodID);
+        functions->CallNonvirtualVoidMethodV(this,obj,clazz,methodID,args);
+        va_end(args);
+    }
+    void CallNonvirtualVoidMethodV(jobject obj, jclass clazz,
+                                   jmethodID methodID,
+                                   va_list args) {
+        functions->CallNonvirtualVoidMethodV(this,obj,clazz,methodID,args);
+    }
+    void CallNonvirtualVoidMethodA(jobject obj, jclass clazz,
+                                   jmethodID methodID,
+                                   const jvalue * args) {
+        functions->CallNonvirtualVoidMethodA(this,obj,clazz,methodID,args);
+    }
+
+    jfieldID GetFieldID(jclass clazz, const char *name,
+                        const char *sig) {
+        return functions->GetFieldID(this,clazz,name,sig);
+    }
+
+    jobject GetObjectField(jobject obj, jfieldID fieldID) {
+        return functions->GetObjectField(this,obj,fieldID);
+    }
+    jboolean GetBooleanField(jobject obj, jfieldID fieldID) {
+        return functions->GetBooleanField(this,obj,fieldID);
+    }
+    jbyte GetByteField(jobject obj, jfieldID fieldID) {
+        return functions->GetByteField(this,obj,fieldID);
+    }
+    jchar GetCharField(jobject obj, jfieldID fieldID) {
+        return functions->GetCharField(this,obj,fieldID);
+    }
+    jshort GetShortField(jobject obj, jfieldID fieldID) {
+        return functions->GetShortField(this,obj,fieldID);
+    }
+    jint GetIntField(jobject obj, jfieldID fieldID) {
+        return functions->GetIntField(this,obj,fieldID);
+    }
+    jlong GetLongField(jobject obj, jfieldID fieldID) {
+        return functions->GetLongField(this,obj,fieldID);
+    }
+    jfloat GetFloatField(jobject obj, jfieldID fieldID) {
+        return functions->GetFloatField(this,obj,fieldID);
+    }
+    jdouble GetDoubleField(jobject obj, jfieldID fieldID) {
+        return functions->GetDoubleField(this,obj,fieldID);
+    }
+
+    void SetObjectField(jobject obj, jfieldID fieldID, jobject val) {
+        functions->SetObjectField(this,obj,fieldID,val);
+    }
+    void SetBooleanField(jobject obj, jfieldID fieldID,
+                         jboolean val) {
+        functions->SetBooleanField(this,obj,fieldID,val);
+    }
+    void SetByteField(jobject obj, jfieldID fieldID,
+                      jbyte val) {
+        functions->SetByteField(this,obj,fieldID,val);
+    }
+    void SetCharField(jobject obj, jfieldID fieldID,
+                      jchar val) {
+        functions->SetCharField(this,obj,fieldID,val);
+    }
+    void SetShortField(jobject obj, jfieldID fieldID,
+                       jshort val) {
+        functions->SetShortField(this,obj,fieldID,val);
+    }
+    void SetIntField(jobject obj, jfieldID fieldID,
+                     jint val) {
+        functions->SetIntField(this,obj,fieldID,val);
+    }
+    void SetLongField(jobject obj, jfieldID fieldID,
+                      jlong val) {
+        functions->SetLongField(this,obj,fieldID,val);
+    }
+    void SetFloatField(jobject obj, jfieldID fieldID,
+                       jfloat val) {
+        functions->SetFloatField(this,obj,fieldID,val);
+    }
+    void SetDoubleField(jobject obj, jfieldID fieldID,
+                        jdouble val) {
+        functions->SetDoubleField(this,obj,fieldID,val);
+    }
+
+    jmethodID GetStaticMethodID(jclass clazz, const char *name,
+                                const char *sig) {
+        return functions->GetStaticMethodID(this,clazz,name,sig);
+    }
+
+    jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID,
+                             ...) {
+        va_list args;
+        jobject result;
+        va_start(args,methodID);
+        result = functions->CallStaticObjectMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jobject CallStaticObjectMethodV(jclass clazz, jmethodID methodID,
+                              va_list args) {
+        return functions->CallStaticObjectMethodV(this,clazz,methodID,args);
+    }
+    jobject CallStaticObjectMethodA(jclass clazz, jmethodID methodID,
+                              const jvalue *args) {
+        return functions->CallStaticObjectMethodA(this,clazz,methodID,args);
+    }
+
+    jboolean CallStaticBooleanMethod(jclass clazz,
+                                     jmethodID methodID, ...) {
+        va_list args;
+        jboolean result;
+        va_start(args,methodID);
+        result = functions->CallStaticBooleanMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jboolean CallStaticBooleanMethodV(jclass clazz,
+                                      jmethodID methodID, va_list args) {
+        return functions->CallStaticBooleanMethodV(this,clazz,methodID,args);
+    }
+    jboolean CallStaticBooleanMethodA(jclass clazz,
+                                      jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticBooleanMethodA(this,clazz,methodID,args);
+    }
+
+    jbyte CallStaticByteMethod(jclass clazz,
+                               jmethodID methodID, ...) {
+        va_list args;
+        jbyte result;
+        va_start(args,methodID);
+        result = functions->CallStaticByteMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jbyte CallStaticByteMethodV(jclass clazz,
+                                jmethodID methodID, va_list args) {
+        return functions->CallStaticByteMethodV(this,clazz,methodID,args);
+    }
+    jbyte CallStaticByteMethodA(jclass clazz,
+                                jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticByteMethodA(this,clazz,methodID,args);
+    }
+
+    jchar CallStaticCharMethod(jclass clazz,
+                               jmethodID methodID, ...) {
+        va_list args;
+        jchar result;
+        va_start(args,methodID);
+        result = functions->CallStaticCharMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jchar CallStaticCharMethodV(jclass clazz,
+                                jmethodID methodID, va_list args) {
+        return functions->CallStaticCharMethodV(this,clazz,methodID,args);
+    }
+    jchar CallStaticCharMethodA(jclass clazz,
+                                jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticCharMethodA(this,clazz,methodID,args);
+    }
+
+    jshort CallStaticShortMethod(jclass clazz,
+                                 jmethodID methodID, ...) {
+        va_list args;
+        jshort result;
+        va_start(args,methodID);
+        result = functions->CallStaticShortMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jshort CallStaticShortMethodV(jclass clazz,
+                                  jmethodID methodID, va_list args) {
+        return functions->CallStaticShortMethodV(this,clazz,methodID,args);
+    }
+    jshort CallStaticShortMethodA(jclass clazz,
+                                  jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticShortMethodA(this,clazz,methodID,args);
+    }
+
+    jint CallStaticIntMethod(jclass clazz,
+                             jmethodID methodID, ...) {
+        va_list args;
+        jint result;
+        va_start(args,methodID);
+        result = functions->CallStaticIntMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jint CallStaticIntMethodV(jclass clazz,
+                              jmethodID methodID, va_list args) {
+        return functions->CallStaticIntMethodV(this,clazz,methodID,args);
+    }
+    jint CallStaticIntMethodA(jclass clazz,
+                              jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticIntMethodA(this,clazz,methodID,args);
+    }
+
+    jlong CallStaticLongMethod(jclass clazz,
+                               jmethodID methodID, ...) {
+        va_list args;
+        jlong result;
+        va_start(args,methodID);
+        result = functions->CallStaticLongMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jlong CallStaticLongMethodV(jclass clazz,
+                                jmethodID methodID, va_list args) {
+        return functions->CallStaticLongMethodV(this,clazz,methodID,args);
+    }
+    jlong CallStaticLongMethodA(jclass clazz,
+                                jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticLongMethodA(this,clazz,methodID,args);
+    }
+
+    jfloat CallStaticFloatMethod(jclass clazz,
+                                 jmethodID methodID, ...) {
+        va_list args;
+        jfloat result;
+        va_start(args,methodID);
+        result = functions->CallStaticFloatMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jfloat CallStaticFloatMethodV(jclass clazz,
+                                  jmethodID methodID, va_list args) {
+        return functions->CallStaticFloatMethodV(this,clazz,methodID,args);
+    }
+    jfloat CallStaticFloatMethodA(jclass clazz,
+                                  jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticFloatMethodA(this,clazz,methodID,args);
+    }
+
+    jdouble CallStaticDoubleMethod(jclass clazz,
+                                   jmethodID methodID, ...) {
+        va_list args;
+        jdouble result;
+        va_start(args,methodID);
+        result = functions->CallStaticDoubleMethodV(this,clazz,methodID,args);
+        va_end(args);
+        return result;
+    }
+    jdouble CallStaticDoubleMethodV(jclass clazz,
+                                    jmethodID methodID, va_list args) {
+        return functions->CallStaticDoubleMethodV(this,clazz,methodID,args);
+    }
+    jdouble CallStaticDoubleMethodA(jclass clazz,
+                                    jmethodID methodID, const jvalue *args) {
+        return functions->CallStaticDoubleMethodA(this,clazz,methodID,args);
+    }
+
+    void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...) {
+        va_list args;
+        va_start(args,methodID);
+        functions->CallStaticVoidMethodV(this,cls,methodID,args);
+        va_end(args);
+    }
+    void CallStaticVoidMethodV(jclass cls, jmethodID methodID,
+                               va_list args) {
+        functions->CallStaticVoidMethodV(this,cls,methodID,args);
+    }
+    void CallStaticVoidMethodA(jclass cls, jmethodID methodID,
+                               const jvalue * args) {
+        functions->CallStaticVoidMethodA(this,cls,methodID,args);
+    }
+
+    jfieldID GetStaticFieldID(jclass clazz, const char *name,
+                              const char *sig) {
+        return functions->GetStaticFieldID(this,clazz,name,sig);
+    }
+    jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticObjectField(this,clazz,fieldID);
+    }
+    jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticBooleanField(this,clazz,fieldID);
+    }
+    jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticByteField(this,clazz,fieldID);
+    }
+    jchar GetStaticCharField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticCharField(this,clazz,fieldID);
+    }
+    jshort GetStaticShortField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticShortField(this,clazz,fieldID);
+    }
+    jint GetStaticIntField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticIntField(this,clazz,fieldID);
+    }
+    jlong GetStaticLongField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticLongField(this,clazz,fieldID);
+    }
+    jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticFloatField(this,clazz,fieldID);
+    }
+    jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) {
+        return functions->GetStaticDoubleField(this,clazz,fieldID);
+    }
+
+    void SetStaticObjectField(jclass clazz, jfieldID fieldID,
+                        jobject value) {
+      functions->SetStaticObjectField(this,clazz,fieldID,value);
+    }
+    void SetStaticBooleanField(jclass clazz, jfieldID fieldID,
+                        jboolean value) {
+      functions->SetStaticBooleanField(this,clazz,fieldID,value);
+    }
+    void SetStaticByteField(jclass clazz, jfieldID fieldID,
+                        jbyte value) {
+      functions->SetStaticByteField(this,clazz,fieldID,value);
+    }
+    void SetStaticCharField(jclass clazz, jfieldID fieldID,
+                        jchar value) {
+      functions->SetStaticCharField(this,clazz,fieldID,value);
+    }
+    void SetStaticShortField(jclass clazz, jfieldID fieldID,
+                        jshort value) {
+      functions->SetStaticShortField(this,clazz,fieldID,value);
+    }
+    void SetStaticIntField(jclass clazz, jfieldID fieldID,
+                        jint value) {
+      functions->SetStaticIntField(this,clazz,fieldID,value);
+    }
+    void SetStaticLongField(jclass clazz, jfieldID fieldID,
+                        jlong value) {
+      functions->SetStaticLongField(this,clazz,fieldID,value);
+    }
+    void SetStaticFloatField(jclass clazz, jfieldID fieldID,
+                        jfloat value) {
+      functions->SetStaticFloatField(this,clazz,fieldID,value);
+    }
+    void SetStaticDoubleField(jclass clazz, jfieldID fieldID,
+                        jdouble value) {
+      functions->SetStaticDoubleField(this,clazz,fieldID,value);
+    }
+
+    jstring NewString(const jchar *unicode, jsize len) {
+        return functions->NewString(this,unicode,len);
+    }
+    jsize GetStringLength(jstring str) {
+        return functions->GetStringLength(this,str);
+    }
+    const jchar *GetStringChars(jstring str, jboolean *isCopy) {
+        return functions->GetStringChars(this,str,isCopy);
+    }
+    void ReleaseStringChars(jstring str, const jchar *chars) {
+        functions->ReleaseStringChars(this,str,chars);
+    }
+
+    jstring NewStringUTF(const char *utf) {
+        return functions->NewStringUTF(this,utf);
+    }
+    jsize GetStringUTFLength(jstring str) {
+        return functions->GetStringUTFLength(this,str);
+    }
+    const char* GetStringUTFChars(jstring str, jboolean *isCopy) {
+        return functions->GetStringUTFChars(this,str,isCopy);
+    }
+    void ReleaseStringUTFChars(jstring str, const char* chars) {
+        functions->ReleaseStringUTFChars(this,str,chars);
+    }
+
+    jsize GetArrayLength(jarray array) {
+        return functions->GetArrayLength(this,array);
+    }
+
+    jobjectArray NewObjectArray(jsize len, jclass clazz,
+                                jobject init) {
+        return functions->NewObjectArray(this,len,clazz,init);
+    }
+    jobject GetObjectArrayElement(jobjectArray array, jsize index) {
+        return functions->GetObjectArrayElement(this,array,index);
+    }
+    void SetObjectArrayElement(jobjectArray array, jsize index,
+                               jobject val) {
+        functions->SetObjectArrayElement(this,array,index,val);
+    }
+
+    jbooleanArray NewBooleanArray(jsize len) {
+        return functions->NewBooleanArray(this,len);
+    }
+    jbyteArray NewByteArray(jsize len) {
+        return functions->NewByteArray(this,len);
+    }
+    jcharArray NewCharArray(jsize len) {
+        return functions->NewCharArray(this,len);
+    }
+    jshortArray NewShortArray(jsize len) {
+        return functions->NewShortArray(this,len);
+    }
+    jintArray NewIntArray(jsize len) {
+        return functions->NewIntArray(this,len);
+    }
+    jlongArray NewLongArray(jsize len) {
+        return functions->NewLongArray(this,len);
+    }
+    jfloatArray NewFloatArray(jsize len) {
+        return functions->NewFloatArray(this,len);
+    }
+    jdoubleArray NewDoubleArray(jsize len) {
+        return functions->NewDoubleArray(this,len);
+    }
+
+    jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy) {
+        return functions->GetBooleanArrayElements(this,array,isCopy);
+    }
+    jbyte * GetByteArrayElements(jbyteArray array, jboolean *isCopy) {
+        return functions->GetByteArrayElements(this,array,isCopy);
+    }
+    jchar * GetCharArrayElements(jcharArray array, jboolean *isCopy) {
+        return functions->GetCharArrayElements(this,array,isCopy);
+    }
+    jshort * GetShortArrayElements(jshortArray array, jboolean *isCopy) {
+        return functions->GetShortArrayElements(this,array,isCopy);
+    }
+    jint * GetIntArrayElements(jintArray array, jboolean *isCopy) {
+        return functions->GetIntArrayElements(this,array,isCopy);
+    }
+    jlong * GetLongArrayElements(jlongArray array, jboolean *isCopy) {
+        return functions->GetLongArrayElements(this,array,isCopy);
+    }
+    jfloat * GetFloatArrayElements(jfloatArray array, jboolean *isCopy) {
+        return functions->GetFloatArrayElements(this,array,isCopy);
+    }
+    jdouble * GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy) {
+        return functions->GetDoubleArrayElements(this,array,isCopy);
+    }
+
+    void ReleaseBooleanArrayElements(jbooleanArray array,
+                                     jboolean *elems,
+                                     jint mode) {
+        functions->ReleaseBooleanArrayElements(this,array,elems,mode);
+    }
+    void ReleaseByteArrayElements(jbyteArray array,
+                                  jbyte *elems,
+                                  jint mode) {
+        functions->ReleaseByteArrayElements(this,array,elems,mode);
+    }
+    void ReleaseCharArrayElements(jcharArray array,
+                                  jchar *elems,
+                                  jint mode) {
+        functions->ReleaseCharArrayElements(this,array,elems,mode);
+    }
+    void ReleaseShortArrayElements(jshortArray array,
+                                   jshort *elems,
+                                   jint mode) {
+        functions->ReleaseShortArrayElements(this,array,elems,mode);
+    }
+    void ReleaseIntArrayElements(jintArray array,
+                                 jint *elems,
+                                 jint mode) {
+        functions->ReleaseIntArrayElements(this,array,elems,mode);
+    }
+    void ReleaseLongArrayElements(jlongArray array,
+                                  jlong *elems,
+                                  jint mode) {
+        functions->ReleaseLongArrayElements(this,array,elems,mode);
+    }
+    void ReleaseFloatArrayElements(jfloatArray array,
+                                   jfloat *elems,
+                                   jint mode) {
+        functions->ReleaseFloatArrayElements(this,array,elems,mode);
+    }
+    void ReleaseDoubleArrayElements(jdoubleArray array,
+                                    jdouble *elems,
+                                    jint mode) {
+        functions->ReleaseDoubleArrayElements(this,array,elems,mode);
+    }
+
+    void GetBooleanArrayRegion(jbooleanArray array,
+                               jsize start, jsize len, jboolean *buf) {
+        functions->GetBooleanArrayRegion(this,array,start,len,buf);
+    }
+    void GetByteArrayRegion(jbyteArray array,
+                            jsize start, jsize len, jbyte *buf) {
+        functions->GetByteArrayRegion(this,array,start,len,buf);
+    }
+    void GetCharArrayRegion(jcharArray array,
+                            jsize start, jsize len, jchar *buf) {
+        functions->GetCharArrayRegion(this,array,start,len,buf);
+    }
+    void GetShortArrayRegion(jshortArray array,
+                             jsize start, jsize len, jshort *buf) {
+        functions->GetShortArrayRegion(this,array,start,len,buf);
+    }
+    void GetIntArrayRegion(jintArray array,
+                           jsize start, jsize len, jint *buf) {
+        functions->GetIntArrayRegion(this,array,start,len,buf);
+    }
+    void GetLongArrayRegion(jlongArray array,
+                            jsize start, jsize len, jlong *buf) {
+        functions->GetLongArrayRegion(this,array,start,len,buf);
+    }
+    void GetFloatArrayRegion(jfloatArray array,
+                             jsize start, jsize len, jfloat *buf) {
+        functions->GetFloatArrayRegion(this,array,start,len,buf);
+    }
+    void GetDoubleArrayRegion(jdoubleArray array,
+                              jsize start, jsize len, jdouble *buf) {
+        functions->GetDoubleArrayRegion(this,array,start,len,buf);
+    }
+
+    void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
+                               const jboolean *buf) {
+        functions->SetBooleanArrayRegion(this,array,start,len,buf);
+    }
+    void SetByteArrayRegion(jbyteArray array, jsize start, jsize len,
+                            const jbyte *buf) {
+        functions->SetByteArrayRegion(this,array,start,len,buf);
+    }
+    void SetCharArrayRegion(jcharArray array, jsize start, jsize len,
+                            const jchar *buf) {
+        functions->SetCharArrayRegion(this,array,start,len,buf);
+    }
+    void SetShortArrayRegion(jshortArray array, jsize start, jsize len,
+                             const jshort *buf) {
+        functions->SetShortArrayRegion(this,array,start,len,buf);
+    }
+    void SetIntArrayRegion(jintArray array, jsize start, jsize len,
+                           const jint *buf) {
+        functions->SetIntArrayRegion(this,array,start,len,buf);
+    }
+    void SetLongArrayRegion(jlongArray array, jsize start, jsize len,
+                            const jlong *buf) {
+        functions->SetLongArrayRegion(this,array,start,len,buf);
+    }
+    void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
+                             const jfloat *buf) {
+        functions->SetFloatArrayRegion(this,array,start,len,buf);
+    }
+    void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
+                              const jdouble *buf) {
+        functions->SetDoubleArrayRegion(this,array,start,len,buf);
+    }
+
+    jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,
+                         jint nMethods) {
+        return functions->RegisterNatives(this,clazz,methods,nMethods);
+    }
+    jint UnregisterNatives(jclass clazz) {
+        return functions->UnregisterNatives(this,clazz);
+    }
+
+    jint MonitorEnter(jobject obj) {
+        return functions->MonitorEnter(this,obj);
+    }
+    jint MonitorExit(jobject obj) {
+        return functions->MonitorExit(this,obj);
+    }
+
+    jint GetJavaVM(JavaVM **vm) {
+        return functions->GetJavaVM(this,vm);
+    }
+
+    void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) {
+        functions->GetStringRegion(this,str,start,len,buf);
+    }
+    void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) {
+        functions->GetStringUTFRegion(this,str,start,len,buf);
+    }
+
+    void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) {
+        return functions->GetPrimitiveArrayCritical(this,array,isCopy);
+    }
+    void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) {
+        functions->ReleasePrimitiveArrayCritical(this,array,carray,mode);
+    }
+
+    const jchar * GetStringCritical(jstring string, jboolean *isCopy) {
+        return functions->GetStringCritical(this,string,isCopy);
+    }
+    void ReleaseStringCritical(jstring string, const jchar *cstring) {
+        functions->ReleaseStringCritical(this,string,cstring);
+    }
+
+    jweak NewWeakGlobalRef(jobject obj) {
+        return functions->NewWeakGlobalRef(this,obj);
+    }
+    void DeleteWeakGlobalRef(jweak ref) {
+        functions->DeleteWeakGlobalRef(this,ref);
+    }
+
+    jboolean ExceptionCheck() {
+        return functions->ExceptionCheck(this);
+    }
+
+    jobject NewDirectByteBuffer(void* address, jlong capacity) {
+        return functions->NewDirectByteBuffer(this, address, capacity);
+    }
+    void* GetDirectBufferAddress(jobject buf) {
+        return functions->GetDirectBufferAddress(this, buf);
+    }
+    jlong GetDirectBufferCapacity(jobject buf) {
+        return functions->GetDirectBufferCapacity(this, buf);
+    }
+    jobjectRefType GetObjectRefType(jobject obj) {
+        return functions->GetObjectRefType(this, obj);
+    }
+
+#endif /* __cplusplus */
+};
+
+typedef struct JavaVMOption {
+    char *optionString;
+    void *extraInfo;
+} JavaVMOption;
+
+typedef struct JavaVMInitArgs {
+    jint version;
+
+    jint nOptions;
+    JavaVMOption *options;
+    jboolean ignoreUnrecognized;
+} JavaVMInitArgs;
+
+typedef struct JavaVMAttachArgs {
+    jint version;
+
+    char *name;
+    jobject group;
+} JavaVMAttachArgs;
+
+/* These will be VM-specific. */
+
+#define JDK1_2
+#define JDK1_4
+
+/* End VM-specific. */
+
+struct JNIInvokeInterface_ {
+    void *reserved0;
+    void *reserved1;
+    void *reserved2;
+
+    jint (JNICALL *DestroyJavaVM)(JavaVM *vm);
+
+    jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args);
+
+    jint (JNICALL *DetachCurrentThread)(JavaVM *vm);
+
+    jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version);
+
+    jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args);
+};
+
+struct JavaVM_ {
+    const struct JNIInvokeInterface_ *functions;
+#ifdef __cplusplus
+
+    jint DestroyJavaVM() {
+        return functions->DestroyJavaVM(this);
+    }
+    jint AttachCurrentThread(void **penv, void *args) {
+        return functions->AttachCurrentThread(this, penv, args);
+    }
+    jint DetachCurrentThread() {
+        return functions->DetachCurrentThread(this);
+    }
+
+    jint GetEnv(void **penv, jint version) {
+        return functions->GetEnv(this, penv, version);
+    }
+    jint AttachCurrentThreadAsDaemon(void **penv, void *args) {
+        return functions->AttachCurrentThreadAsDaemon(this, penv, args);
+    }
+#endif
+};
+
+#ifdef _JNI_IMPLEMENTATION_
+#define _JNI_IMPORT_OR_EXPORT_ JNIEXPORT
+#else
+#define _JNI_IMPORT_OR_EXPORT_ JNIIMPORT
+#endif
+_JNI_IMPORT_OR_EXPORT_ jint JNICALL
+JNI_GetDefaultJavaVMInitArgs(void *args);
+
+_JNI_IMPORT_OR_EXPORT_ jint JNICALL
+JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
+
+_JNI_IMPORT_OR_EXPORT_ jint JNICALL
+JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);
+
+/* Defined by native libraries. */
+JNIEXPORT jint JNICALL
+JNI_OnLoad(JavaVM *vm, void *reserved);
+
+JNIEXPORT void JNICALL
+JNI_OnUnload(JavaVM *vm, void *reserved);
+
+#define JNI_VERSION_1_1 0x00010001
+#define JNI_VERSION_1_2 0x00010002
+#define JNI_VERSION_1_4 0x00010004
+#define JNI_VERSION_1_6 0x00010006
+#define JNI_VERSION_1_8 0x00010008
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_JAVASOFT_JNI_H_ */
diff --git a/java/arm-linux/linux/jni_md.h b/java/arm-linux/linux/jni_md.h
new file mode 100644
index 0000000..80eedf3
--- /dev/null
+++ b/java/arm-linux/linux/jni_md.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef _JAVASOFT_JNI_MD_H_
+#define _JAVASOFT_JNI_MD_H_
+
+#ifndef __has_attribute
+  #define __has_attribute(x) 0
+#endif
+#if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility)
+  #define JNIEXPORT     __attribute__((visibility("default")))
+  #define JNIIMPORT     __attribute__((visibility("default")))
+#else
+  #define JNIEXPORT
+  #define JNIIMPORT
+#endif
+
+#define JNICALL
+
+typedef int jint;
+#ifdef _LP64 /* 64-bit Solaris */
+typedef long jlong;
+#else
+typedef long long jlong;
+#endif
+
+typedef signed char jbyte;
+
+#endif /* !_JAVASOFT_JNI_MD_H_ */
diff --git a/java/java.gradle b/java/java.gradle
new file mode 100644
index 0000000..8ff6a7b
--- /dev/null
+++ b/java/java.gradle
@@ -0,0 +1,134 @@
+import org.gradle.internal.os.OperatingSystem
+
+apply plugin: 'java'
+
+def generatedJNIHeaderLoc = '../build/include'
+
+sourceSets {
+    main {
+        java {
+            srcDirs = ['../java/src']
+        }
+    }
+}
+
+jar {
+    description = 'Generates NetworkTables jar, with the JNI shared libraries embedded'
+    baseName = 'ntcore'
+    classifier = "$buildPlatform"
+
+    dependsOn { classes }
+    binaries.withType(SharedLibraryBinary) { binary ->
+        from(file(binary.sharedLibraryFile)) {
+            into getPlatformPath(binary)
+        }
+    }
+}
+
+project.tasks.whenTaskAdded { task-> 
+    if (isArm) {
+        if (task.name == 'ntcoreSharedLibrary') jar.dependsOn task
+    } else {
+        if (task.name == 'x64NtcoreSharedLibrary' || task.name == 'x86NtcoreSharedLibrary') 
+            jar.dependsOn task
+    }
+}
+
+task networktablesJavaSource(type: Jar, dependsOn: classes) {
+    description = 'Generates the source jar for NetworkTables java'
+    group = 'WPILib'
+    baseName = 'ntcore'
+    classifier = "sources"
+    from sourceSets.main.allJava
+}
+
+task networktablesJavadoc(type: Jar, dependsOn: javadoc) {
+    description = 'Generates the javadoc jar for NetworkTables java'
+    group = 'WPILib'
+    baseName = 'ntcore'
+    classifier = "javadoc"
+    from javadoc.destinationDir
+}
+
+build.dependsOn networktablesJavaSource
+build.dependsOn networktablesJavadoc
+
+/**
+ * Generates the JNI headers
+ */
+task jniHeadersNetworkTables {
+    description = 'Generates JNI headers from edu.wpi.first.wpilibj.networktables.*'
+    group = 'WPILib'
+    def outputFolder = file(generatedJNIHeaderLoc)
+    inputs.files sourceSets.main.output
+    outputs.file outputFolder
+    doLast {
+        outputFolder.mkdirs()
+        exec {
+            executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah')
+            args '-d', outputFolder
+            args '-classpath', sourceSets.main.output.classesDir
+            args 'edu.wpi.first.wpilibj.networktables.NetworkTablesJNI'
+        }
+    }
+}
+
+clean {
+    delete generatedJNIHeaderLoc
+}
+
+compileJava {
+    options.compilerArgs << '-Xlint:unchecked'
+}
+
+javadoc {
+    options.addStringOption('Xdoclint:none', '-quiet')
+}
+
+// This creates a lambda that the main build.gradle can access, which sets up the JNI includes for the
+// target build platform. This lambda is exposed as a property in the main build.gradle.
+ext.setupJniIncludes = { binaries ->
+    def platformSpecificIncludeFlag = { loc, cppCompiler ->
+        if (OperatingSystem.current().isWindows()) {
+            cppCompiler.args "/I$loc"
+        } else {
+            cppCompiler.args '-I', loc
+        }
+    }
+    binaries.all {
+        tasks.withType(CppCompile) {
+            if (buildPlatform == 'arm') {
+                cppCompiler.args '-I', file('../java/arm-linux').absolutePath
+                cppCompiler.args '-I', file('../java/arm-linux/linux').absolutePath
+            } else {
+                def jdkLocation = org.gradle.internal.jvm.Jvm.current().javaHome
+                platformSpecificIncludeFlag("${jdkLocation}/include", cppCompiler)
+
+                if (targetPlatform.operatingSystem.macOsX) {
+                    platformSpecificIncludeFlag("${jdkLocation}/include/darwin", cppCompiler)
+                } else if (targetPlatform.operatingSystem.linux) {
+                    platformSpecificIncludeFlag("${jdkLocation}/include/linux", cppCompiler)
+                } else if (targetPlatform.operatingSystem.windows) {
+                    platformSpecificIncludeFlag("${jdkLocation}/include/win32", cppCompiler)
+                } else if (targetPlatform.operatingSystem.freeBSD) {
+                    platformSpecificIncludeFlag("${jdkLocation}/include/freebsd", cppCompiler)
+                } else if (file("$jdkLocation/include/darwin").exists()) {
+                    // TODO: As of Gradle 2.8, targetPlatform.operatingSystem.macOsX returns false
+                    // on El Capitan. We therefore manually test for the darwin folder and include it
+                    // if it exists
+                    platformSpecificIncludeFlag("${jdkLocation}/include/darwin", cppCompiler)
+                }
+            }
+
+            jniHeadersNetworkTables.outputs.files.each { file ->
+                if (buildPlatform == 'arm') {
+                    cppCompiler.args '-I', file.getPath()
+                } else {
+                    platformSpecificIncludeFlag(file.getPath(), cppCompiler)
+                }
+            }
+
+            dependsOn jniHeadersNetworkTables
+        }
+    }
+}
diff --git a/java/lib/NetworkTablesJNI.cpp b/java/lib/NetworkTablesJNI.cpp
new file mode 100644
index 0000000..d235fd3
--- /dev/null
+++ b/java/lib/NetworkTablesJNI.cpp
@@ -0,0 +1,1454 @@
+#include <jni.h>
+#include <atomic>
+#include <cassert>
+#include <condition_variable>
+#include <mutex>
+#include <sstream>
+#include <queue>
+#include <thread>
+
+#include "edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h"
+#include "ntcore.h"
+#include "atomic_static.h"
+
+//
+// Globals and load/unload
+//
+
+// Used for callback.
+static JavaVM *jvm = nullptr;
+static jclass booleanCls = nullptr;
+static jclass doubleCls = nullptr;
+static jclass stringCls = nullptr;
+static jclass connectionInfoCls = nullptr;
+static jclass entryInfoCls = nullptr;
+static jclass keyNotDefinedEx = nullptr;
+static jclass persistentEx = nullptr;
+// Thread-attached environment for listener callbacks.
+static JNIEnv *listenerEnv = nullptr;
+
+static void ListenerOnStart() {
+  if (!jvm) return;
+  JNIEnv *env;
+  if (jvm->AttachCurrentThread(reinterpret_cast<void **>(&env),
+                               nullptr) != JNI_OK)
+    return;
+  if (!env || !env->functions) return;
+  listenerEnv = env;
+}
+
+static void ListenerOnExit() {
+  listenerEnv = nullptr;
+  if (!jvm) return;
+  jvm->DetachCurrentThread();
+}
+
+extern "C" {
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+  jvm = vm;
+
+  JNIEnv *env;
+  if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
+    return JNI_ERR;
+
+  // Cache references to classes
+  jclass local;
+
+  local = env->FindClass("java/lang/Boolean");
+  if (!local) return JNI_ERR;
+  booleanCls = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!booleanCls) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  local = env->FindClass("java/lang/Double");
+  if (!local) return JNI_ERR;
+  doubleCls = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!doubleCls) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  local = env->FindClass("java/lang/String");
+  if (!local) return JNI_ERR;
+  stringCls = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!stringCls) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  local = env->FindClass("edu/wpi/first/wpilibj/networktables/ConnectionInfo");
+  if (!local) return JNI_ERR;
+  connectionInfoCls = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!connectionInfoCls) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  local = env->FindClass("edu/wpi/first/wpilibj/networktables/EntryInfo");
+  if (!local) return JNI_ERR;
+  entryInfoCls = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!entryInfoCls) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  local =
+      env->FindClass("edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined");
+  keyNotDefinedEx = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!keyNotDefinedEx) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  local =
+      env->FindClass("edu/wpi/first/wpilibj/networktables/PersistentException");
+  persistentEx = static_cast<jclass>(env->NewGlobalRef(local));
+  if (!persistentEx) return JNI_ERR;
+  env->DeleteLocalRef(local);
+
+  // Initial configuration of listener start/exit
+  nt::SetListenerOnStart(ListenerOnStart);
+  nt::SetListenerOnExit(ListenerOnExit);
+
+  return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
+  JNIEnv *env;
+  if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
+    return;
+  // Delete global references
+  if (booleanCls) env->DeleteGlobalRef(booleanCls);
+  if (doubleCls) env->DeleteGlobalRef(doubleCls);
+  if (stringCls) env->DeleteGlobalRef(stringCls);
+  if (connectionInfoCls) env->DeleteGlobalRef(connectionInfoCls);
+  if (entryInfoCls) env->DeleteGlobalRef(entryInfoCls);
+  if (keyNotDefinedEx) env->DeleteGlobalRef(keyNotDefinedEx);
+  if (persistentEx) env->DeleteGlobalRef(persistentEx);
+  jvm = nullptr;
+}
+
+}  // extern "C"
+
+//
+// Helper class to automatically clean up a local reference
+//
+template <typename T>
+class JavaLocal {
+ public:
+  JavaLocal(JNIEnv *env, T obj) : m_env(env), m_obj(obj) {}
+  ~JavaLocal() {
+    if (m_obj) m_env->DeleteLocalRef(m_obj);
+  }
+  operator T() { return m_obj; }
+  T obj() { return m_obj; }
+
+ private:
+  JNIEnv *m_env;
+  T m_obj;
+};
+
+//
+// Helper class to create and clean up a global reference
+//
+template <typename T>
+class JavaGlobal {
+ public:
+  JavaGlobal(JNIEnv *env, T obj)
+      : m_obj(static_cast<T>(env->NewGlobalRef(obj))) {}
+  ~JavaGlobal() {
+    if (!jvm || nt::NotifierDestroyed()) return;
+    JNIEnv *env;
+    if (jvm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
+        JNI_OK)
+      return;
+    if (!env || !env->functions) return;
+    env->DeleteGlobalRef(m_obj);
+    jvm->DetachCurrentThread();
+  }
+  operator T() { return m_obj; }
+  T obj() { return m_obj; }
+
+ private:
+  T m_obj;
+};
+
+//
+// Helper class to create and clean up a weak global reference
+//
+template <typename T>
+class JavaWeakGlobal {
+ public:
+  JavaWeakGlobal(JNIEnv *env, T obj)
+      : m_obj(static_cast<T>(env->NewWeakGlobalRef(obj))) {}
+  ~JavaWeakGlobal() {
+    if (!jvm || nt::NotifierDestroyed()) return;
+    JNIEnv *env;
+    if (jvm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
+        JNI_OK)
+      return;
+    if (!env || !env->functions) return;
+    env->DeleteWeakGlobalRef(m_obj);
+    jvm->DetachCurrentThread();
+  }
+  JavaLocal<T> obj(JNIEnv *env) {
+    return JavaLocal<T>(env, env->NewLocalRef(m_obj));
+  }
+
+ private:
+  T m_obj;
+};
+
+//
+// Conversions from Java objects to C++
+//
+
+class JavaStringRef {
+ public:
+  JavaStringRef(JNIEnv *env, jstring str)
+      : m_env(env),
+        m_jstr(str),
+        m_str(env->GetStringUTFChars(str, nullptr)) {}
+  ~JavaStringRef() { m_env->ReleaseStringUTFChars(m_jstr, m_str); }
+
+  operator nt::StringRef() const { return nt::StringRef(m_str); }
+  nt::StringRef str() const { return nt::StringRef(m_str); }
+  const char *c_str() const { return m_str; }
+
+ private:
+  JNIEnv *m_env;
+  jstring m_jstr;
+  const char *m_str;
+};
+
+class JavaByteRef {
+ public:
+  JavaByteRef(JNIEnv *env, jbyteArray jarr)
+      : m_env(env),
+        m_jarr(jarr),
+        m_elements(env->GetByteArrayElements(jarr, nullptr)),
+        m_size(env->GetArrayLength(jarr)) {}
+  ~JavaByteRef() {
+    m_env->ReleaseByteArrayElements(m_jarr, m_elements, JNI_ABORT);
+  }
+
+  operator nt::StringRef() const {
+    return nt::StringRef(reinterpret_cast<char *>(m_elements), m_size);
+  }
+
+ private:
+  JNIEnv *m_env;
+  jbyteArray m_jarr;
+  jbyte *m_elements;
+  size_t m_size;
+};
+
+class JavaByteRefBB {
+ public:
+  JavaByteRefBB(JNIEnv *env, jobject bb, int len)
+      : m_elements(env->GetDirectBufferAddress(bb)), m_size(len) {}
+
+  operator nt::StringRef() const {
+    return nt::StringRef(reinterpret_cast<char *>(m_elements), m_size);
+  }
+
+ private:
+  void *m_elements;
+  size_t m_size;
+};
+
+std::shared_ptr<nt::Value> FromJavaRaw(JNIEnv *env, jbyteArray jarr) {
+  size_t len = env->GetArrayLength(jarr);
+  jbyte *elements =
+      static_cast<jbyte *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
+  if (!elements) return nullptr;
+  auto rv = nt::Value::MakeRaw(
+      nt::StringRef(reinterpret_cast<char *>(elements), len));
+  env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
+  return rv;
+}
+
+std::shared_ptr<nt::Value> FromJavaRawBB(JNIEnv *env, jobject jbb, int len) {
+  void* elements = env->GetDirectBufferAddress(jbb);
+  if (!elements) return nullptr;
+  auto rv = nt::Value::MakeRaw(
+      nt::StringRef(reinterpret_cast<char *>(elements), len));
+  return rv;
+}
+
+std::shared_ptr<nt::Value> FromJavaRpc(JNIEnv *env, jbyteArray jarr) {
+  size_t len = env->GetArrayLength(jarr);
+  jbyte *elements =
+      static_cast<jbyte *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
+  if (!elements) return nullptr;
+  auto rv = nt::Value::MakeRpc(
+      nt::StringRef(reinterpret_cast<char *>(elements), len));
+  env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
+  return rv;
+}
+
+std::shared_ptr<nt::Value> FromJavaBooleanArray(JNIEnv *env,
+                                                jbooleanArray jarr) {
+  size_t len = env->GetArrayLength(jarr);
+  std::vector<int> arr;
+  arr.reserve(len);
+  jboolean *elements =
+      static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
+  if (!elements) return nullptr;
+  for (size_t i = 0; i < len; ++i) arr.push_back(elements[i]);
+  env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
+  return nt::Value::MakeBooleanArray(arr);
+}
+
+std::shared_ptr<nt::Value> FromJavaDoubleArray(JNIEnv *env, jdoubleArray jarr) {
+  size_t len = env->GetArrayLength(jarr);
+  jdouble *elements =
+      static_cast<jdouble *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
+  if (!elements) return nullptr;
+  auto rv = nt::Value::MakeDoubleArray(nt::ArrayRef<double>(elements, len));
+  env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
+  return rv;
+}
+
+std::shared_ptr<nt::Value> FromJavaStringArray(JNIEnv *env, jobjectArray jarr) {
+  size_t len = env->GetArrayLength(jarr);
+  std::vector<std::string> arr;
+  arr.reserve(len);
+  for (size_t i = 0; i < len; ++i) {
+    JavaLocal<jstring> elem(
+        env, static_cast<jstring>(env->GetObjectArrayElement(jarr, i)));
+    if (!elem) return nullptr;
+    arr.push_back(JavaStringRef(env, elem).str());
+  }
+  return nt::Value::MakeStringArray(std::move(arr));
+}
+
+//
+// Conversions from C++ to Java objects
+//
+
+static inline jstring ToJavaString(JNIEnv *env, nt::StringRef str) {
+  // fastpath if string already null terminated; if not, need to make a copy
+  if (str.data()[str.size()] == '\0')
+    return env->NewStringUTF(str.data());
+  else
+    return env->NewStringUTF(str.str().c_str());
+}
+
+static jbyteArray ToJavaByteArray(JNIEnv *env, nt::StringRef str) {
+  jbyteArray jarr = env->NewByteArray(str.size());
+  if (!jarr) return nullptr;
+  env->SetByteArrayRegion(jarr, 0, str.size(),
+                          reinterpret_cast<const jbyte *>(str.data()));
+  return jarr;
+}
+
+static jbooleanArray ToJavaBooleanArray(JNIEnv *env, nt::ArrayRef<int> arr)
+{
+  jbooleanArray jarr = env->NewBooleanArray(arr.size());
+  if (!jarr) return nullptr;
+  jboolean *elements =
+      static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
+  if (!elements) return nullptr;
+  for (size_t i = 0; i < arr.size(); ++i)
+    elements[i] = arr[i];
+  env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
+  return jarr;
+}
+
+static jdoubleArray ToJavaDoubleArray(JNIEnv *env, nt::ArrayRef<double> arr)
+{
+  jdoubleArray jarr = env->NewDoubleArray(arr.size());
+  if (!jarr) return nullptr;
+  env->SetDoubleArrayRegion(jarr, 0, arr.size(), arr.data());
+  return jarr;
+}
+
+static jobjectArray ToJavaStringArray(JNIEnv *env,
+                                      nt::ArrayRef<std::string> arr) {
+  jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
+  if (!jarr) return nullptr;
+  for (size_t i = 0; i < arr.size(); ++i) {
+    JavaLocal<jstring> elem(env, env->NewStringUTF(arr[i].c_str()));
+    env->SetObjectArrayElement(jarr, i, elem.obj());
+  }
+  return jarr;
+}
+
+static jobject ToJavaObject(JNIEnv *env, const nt::Value& value) {
+  static jmethodID booleanConstructor = nullptr;
+  static jmethodID doubleConstructor = nullptr;
+  if (!booleanConstructor)
+    booleanConstructor = env->GetMethodID(booleanCls, "<init>", "(Z)V");
+  if (!doubleConstructor)
+    doubleConstructor = env->GetMethodID(doubleCls, "<init>", "(D)V");
+
+  switch (value.type()) {
+    case NT_BOOLEAN:
+      return env->NewObject(booleanCls, booleanConstructor,
+                            (jboolean)(value.GetBoolean() ? 1 : 0));
+    case NT_DOUBLE:
+      return env->NewObject(doubleCls, doubleConstructor,
+                            (jdouble)value.GetDouble());
+    case NT_STRING:
+      return ToJavaString(env, value.GetString());
+    case NT_RAW:
+      return ToJavaByteArray(env, value.GetRaw());
+    case NT_BOOLEAN_ARRAY:
+      return ToJavaBooleanArray(env, value.GetBooleanArray());
+    case NT_DOUBLE_ARRAY:
+      return ToJavaDoubleArray(env, value.GetDoubleArray());
+    case NT_STRING_ARRAY:
+      return ToJavaStringArray(env, value.GetStringArray());
+    case NT_RPC:
+      return ToJavaByteArray(env, value.GetRpc());
+    default:
+      return nullptr;
+  }
+}
+
+static jobject ToJavaObject(JNIEnv *env, const nt::ConnectionInfo &info) {
+  static jmethodID constructor =
+      env->GetMethodID(connectionInfoCls, "<init>",
+                       "(Ljava/lang/String;Ljava/lang/String;IJI)V");
+  JavaLocal<jstring> remote_id(env, ToJavaString(env, info.remote_id));
+  JavaLocal<jstring> remote_name(env, ToJavaString(env, info.remote_name));
+  return env->NewObject(connectionInfoCls, constructor, remote_id.obj(),
+                        remote_name.obj(), (jint)info.remote_port,
+                        (jlong)info.last_update, (jint)info.protocol_version);
+}
+
+static jobject ToJavaObject(JNIEnv *env, const nt::EntryInfo &info) {
+  static jmethodID constructor =
+      env->GetMethodID(entryInfoCls, "<init>", "(Ljava/lang/String;IIJ)V");
+  JavaLocal<jstring> name(env, ToJavaString(env, info.name));
+  return env->NewObject(entryInfoCls, constructor, name.obj(), (jint)info.type,
+                        (jint)info.flags, (jlong)info.last_change);
+}
+
+//
+// Exception throwers
+//
+
+static void ThrowTableKeyNotDefined(JNIEnv *env, jstring key) {
+  static jmethodID constructor = nullptr;
+  if (!constructor)
+    constructor =
+        env->GetMethodID(keyNotDefinedEx, "<init>", "(Ljava/lang/String;)V");
+  jobject exception = env->NewObject(keyNotDefinedEx, constructor, key);
+  env->Throw(static_cast<jthrowable>(exception));
+}
+
+extern "C" {
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    containsKey
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_containsKey
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val) return false;
+  return true;
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getType
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getType
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val) return NT_UNASSIGNED;
+  return val->type();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putBoolean
+ * Signature: (Ljava/lang/String;Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBoolean
+  (JNIEnv *env, jclass, jstring key, jboolean value)
+{
+  return nt::SetEntryValue(JavaStringRef(env, key),
+                           nt::Value::MakeBoolean(value != JNI_FALSE));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putDouble
+ * Signature: (Ljava/lang/String;D)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDouble
+  (JNIEnv *env, jclass, jstring key, jdouble value)
+{
+  return nt::SetEntryValue(JavaStringRef(env, key),
+                           nt::Value::MakeDouble(value));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putString
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putString
+  (JNIEnv *env, jclass, jstring key, jstring value)
+{
+  return nt::SetEntryValue(JavaStringRef(env, key),
+                           nt::Value::MakeString(JavaStringRef(env, value)));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putRaw
+ * Signature: (Ljava/lang/String;[B)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw__Ljava_lang_String_2_3B
+  (JNIEnv *env, jclass, jstring key, jbyteArray value)
+{
+  auto v = FromJavaRaw(env, value);
+  if (!v) return false;
+  return nt::SetEntryValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putRaw
+ * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I
+  (JNIEnv *env, jclass, jstring key, jobject value, jint len)
+{
+  auto v = FromJavaRawBB(env, value, len);
+  if (!v) return false;
+  return nt::SetEntryValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putBooleanArray
+ * Signature: (Ljava/lang/String;[Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBooleanArray
+  (JNIEnv *env, jclass, jstring key, jbooleanArray value)
+{
+  auto v = FromJavaBooleanArray(env, value);
+  if (!v) return false;
+  return nt::SetEntryValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putDoubleArray
+ * Signature: (Ljava/lang/String;[D)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDoubleArray
+  (JNIEnv *env, jclass, jstring key, jdoubleArray value)
+{
+  auto v = FromJavaDoubleArray(env, value);
+  if (!v) return false;
+  return nt::SetEntryValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    putStringArray
+ * Signature: (Ljava/lang/String;[Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putStringArray
+  (JNIEnv *env, jclass, jstring key, jobjectArray value)
+{
+  auto v = FromJavaStringArray(env, value);
+  if (!v) return false;
+  return nt::SetEntryValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutBoolean
+ * Signature: (Ljava/lang/String;Z)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBoolean
+  (JNIEnv *env, jclass, jstring key, jboolean value)
+{
+  nt::SetEntryTypeValue(JavaStringRef(env, key), nt::Value::MakeBoolean(value != JNI_FALSE));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutDouble
+ * Signature: (Ljava/lang/String;D)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDouble
+  (JNIEnv *env, jclass, jstring key, jdouble value)
+{
+  nt::SetEntryTypeValue(JavaStringRef(env, key), nt::Value::MakeDouble(value));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutString
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutString
+  (JNIEnv *env, jclass, jstring key, jstring value)
+{
+  nt::SetEntryTypeValue(JavaStringRef(env, key),
+                        nt::Value::MakeString(JavaStringRef(env, value)));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutRaw
+ * Signature: (Ljava/lang/String;[B)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw__Ljava_lang_String_2_3B
+  (JNIEnv *env, jclass, jstring key, jbyteArray value)
+{
+  auto v = FromJavaRaw(env, value);
+  if (!v) return;
+  nt::SetEntryTypeValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutRaw
+ * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I
+  (JNIEnv *env, jclass, jstring key, jobject value, jint len)
+{
+  auto v = FromJavaRawBB(env, value, len);
+  if (!v) return;
+  nt::SetEntryTypeValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutBooleanArray
+ * Signature: (Ljava/lang/String;[Z)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBooleanArray
+  (JNIEnv *env, jclass, jstring key, jbooleanArray value)
+{
+  auto v = FromJavaBooleanArray(env, value);
+  if (!v) return;
+  nt::SetEntryTypeValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutDoubleArray
+ * Signature: (Ljava/lang/String;[D)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDoubleArray
+  (JNIEnv *env, jclass, jstring key, jdoubleArray value)
+{
+  auto v = FromJavaDoubleArray(env, value);
+  if (!v) return;
+  nt::SetEntryTypeValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    forcePutStringArray
+ * Signature: (Ljava/lang/String;[Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutStringArray
+  (JNIEnv *env, jclass, jstring key, jobjectArray value)
+{
+  auto v = FromJavaStringArray(env, value);
+  if (!v) return;
+  nt::SetEntryTypeValue(JavaStringRef(env, key), v);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getValue
+ * Signature: (Ljava/lang/String;)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaObject(env, *val);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getBoolean
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsBoolean()) {
+    ThrowTableKeyNotDefined(env, key);
+    return false;
+  }
+  return val->GetBoolean();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getDouble
+ * Signature: (Ljava/lang/String;)D
+ */
+JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsDouble()) {
+    ThrowTableKeyNotDefined(env, key);
+    return 0;
+  }
+  return val->GetDouble();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getString
+ * Signature: (Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsString()) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaString(env, val->GetString());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getRaw
+ * Signature: (Ljava/lang/String;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsRaw()) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaByteArray(env, val->GetRaw());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getBooleanArray
+ * Signature: (Ljava/lang/String;)[Z
+ */
+JNIEXPORT jbooleanArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsBooleanArray()) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaBooleanArray(env, val->GetBooleanArray());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getDoubleArray
+ * Signature: (Ljava/lang/String;)[D
+ */
+JNIEXPORT jdoubleArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsDoubleArray()) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaDoubleArray(env, val->GetDoubleArray());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getStringArray
+ * Signature: (Ljava/lang/String;)[Ljava/lang/String;
+ */
+JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsStringArray()) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaStringArray(env, val->GetStringArray());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getValue
+ * Signature: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue__Ljava_lang_String_2Ljava_lang_Object_2
+  (JNIEnv *env, jclass, jstring key, jobject defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val) return defaultValue;
+  return ToJavaObject(env, *val);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getBoolean
+ * Signature: (Ljava/lang/String;Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean__Ljava_lang_String_2Z
+  (JNIEnv *env, jclass, jstring key, jboolean defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsBoolean()) return defaultValue;
+  return val->GetBoolean();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getDouble
+ * Signature: (Ljava/lang/String;D)D
+ */
+JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble__Ljava_lang_String_2D
+  (JNIEnv *env, jclass, jstring key, jdouble defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsDouble()) return defaultValue;
+  return val->GetDouble();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getString
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString__Ljava_lang_String_2Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key, jstring defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsString()) return defaultValue;
+  return ToJavaString(env, val->GetString());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getRaw
+ * Signature: (Ljava/lang/String;[B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw__Ljava_lang_String_2_3B
+  (JNIEnv *env, jclass, jstring key, jbyteArray defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsRaw()) return defaultValue;
+  return ToJavaByteArray(env, val->GetRaw());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getBooleanArray
+ * Signature: (Ljava/lang/String;[Z)[Z
+ */
+JNIEXPORT jbooleanArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray__Ljava_lang_String_2_3Z
+  (JNIEnv *env, jclass, jstring key, jbooleanArray defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsBooleanArray()) return defaultValue;
+  return ToJavaBooleanArray(env, val->GetBooleanArray());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getDoubleArray
+ * Signature: (Ljava/lang/String;[D)[D
+ */
+JNIEXPORT jdoubleArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray__Ljava_lang_String_2_3D
+  (JNIEnv *env, jclass, jstring key, jdoubleArray defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsDoubleArray()) return defaultValue;
+  return ToJavaDoubleArray(env, val->GetDoubleArray());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getStringArray
+ * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
+ */
+JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray__Ljava_lang_String_2_3Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key, jobjectArray defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsStringArray()) return defaultValue;
+  return ToJavaStringArray(env, val->GetStringArray());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    setEntryFlags
+ * Signature: (Ljava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setEntryFlags
+  (JNIEnv *env, jclass, jstring key, jint flags)
+{
+  nt::SetEntryFlags(JavaStringRef(env, key), flags);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getEntryFlags
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntryFlags
+  (JNIEnv *env, jclass, jstring key)
+{
+  return nt::GetEntryFlags(JavaStringRef(env, key));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    deleteEntry
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteEntry
+  (JNIEnv *env, jclass, jstring key)
+{
+  nt::DeleteEntry(JavaStringRef(env, key));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    deleteAllEntries
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteAllEntries
+  (JNIEnv *, jclass)
+{
+  nt::DeleteAllEntries();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getEntries
+ * Signature: (Ljava/lang/String;I)[Ledu/wpi/first/wpilibj/networktables/EntryInfo;
+ */
+JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntries
+  (JNIEnv *env, jclass, jstring prefix, jint types)
+{
+  auto arr = nt::GetEntryInfo(JavaStringRef(env, prefix), types);
+  jobjectArray jarr = env->NewObjectArray(arr.size(), entryInfoCls, nullptr);
+  if (!jarr) return nullptr;
+  for (size_t i = 0; i < arr.size(); ++i) {
+    JavaLocal<jobject> jelem(env, ToJavaObject(env, arr[i]));
+    env->SetObjectArrayElement(jarr, i, jelem);
+  }
+  return jarr;
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    flush
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_flush
+  (JNIEnv *, jclass)
+{
+  nt::Flush();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    addEntryListener
+ * Signature: (Ljava/lang/String;Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/EntryListenerFunction;Z)I
+ */
+JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addEntryListener
+  (JNIEnv *envouter, jclass, jstring prefix, jobject listener, jint flags)
+{
+  // the shared pointer to the weak global will keep it around until the
+  // entry listener is destroyed
+  auto listener_global =
+      std::make_shared<JavaGlobal<jobject>>(envouter, listener);
+
+  // cls is a temporary here; cannot be used within callback functor
+	jclass cls = envouter->GetObjectClass(listener);
+  if (!cls) return 0;
+
+  // method ids, on the other hand, are safe to retain
+  jmethodID mid = envouter->GetMethodID(
+      cls, "apply", "(ILjava/lang/String;Ljava/lang/Object;I)V");
+  if (!mid) return 0;
+
+  return nt::AddEntryListener(
+      JavaStringRef(envouter, prefix),
+      [=](unsigned int uid, nt::StringRef name,
+          std::shared_ptr<nt::Value> value, unsigned int flags_) {
+        JNIEnv *env = listenerEnv;
+        if (!env || !env->functions) return;
+
+        // get the handler
+        auto handler = listener_global->obj();
+
+        // convert the value into the appropriate Java type
+        jobject jobj = ToJavaObject(env, *value);
+        if (!jobj) return;
+
+        if (env->ExceptionCheck()) {
+          env->ExceptionDescribe();
+          env->ExceptionClear();
+          return;
+        }
+        env->CallVoidMethod(handler, mid, (jint)uid, ToJavaString(env, name),
+                            jobj, (jint)(flags_));
+        if (env->ExceptionCheck()) {
+          env->ExceptionDescribe();
+          env->ExceptionClear();
+        }
+      },
+      flags);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    removeEntryListener
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeEntryListener
+  (JNIEnv *, jclass, jint entryListenerUid)
+{
+  nt::RemoveEntryListener(entryListenerUid);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    addConnectionListener
+ * Signature: (Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/ConnectionListenerFunction;Z)I
+ */
+JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addConnectionListener
+  (JNIEnv *envouter, jclass, jobject listener, jboolean immediateNotify)
+{
+  // the shared pointer to the weak global will keep it around until the
+  // entry listener is destroyed
+  auto listener_global =
+      std::make_shared<JavaGlobal<jobject>>(envouter, listener);
+
+  // cls is a temporary here; cannot be used within callback functor
+	jclass cls = envouter->GetObjectClass(listener);
+  if (!cls) return 0;
+
+  // method ids, on the other hand, are safe to retain
+  jmethodID mid = envouter->GetMethodID(
+      cls, "apply", "(IZLedu/wpi/first/wpilibj/networktables/ConnectionInfo;)V");
+  if (!mid) return 0;
+
+  return nt::AddConnectionListener(
+      [=](unsigned int uid, bool connected, const nt::ConnectionInfo& conn) {
+        JNIEnv *env = listenerEnv;
+        if (!env || !env->functions) return;
+
+        // get the handler
+        auto handler = listener_global->obj();
+        //if (!handler) goto done; // can happen due to weak reference
+
+        // convert into the appropriate Java type
+        jobject jobj = ToJavaObject(env, conn);
+        if (!jobj) return;
+
+        if (env->ExceptionCheck()) {
+          env->ExceptionDescribe();
+          env->ExceptionClear();
+          return;
+        }
+        env->CallVoidMethod(handler, mid, (jint)uid,
+                            (jboolean)(connected ? 1 : 0), jobj);
+        if (env->ExceptionCheck()) {
+          env->ExceptionDescribe();
+          env->ExceptionClear();
+        }
+      },
+      immediateNotify != JNI_FALSE);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    removeConnectionListener
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeConnectionListener
+  (JNIEnv *, jclass, jint connListenerUid)
+{
+  nt::RemoveConnectionListener(connListenerUid);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getRpc
+ * Signature: (Ljava/lang/String;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc__Ljava_lang_String_2
+  (JNIEnv *env, jclass, jstring key)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsRpc()) {
+    ThrowTableKeyNotDefined(env, key);
+    return nullptr;
+  }
+  return ToJavaByteArray(env, val->GetRpc());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getRpc
+ * Signature: (Ljava/lang/String;[B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc__Ljava_lang_String_2_3B
+  (JNIEnv *env, jclass, jstring key, jbyteArray defaultValue)
+{
+  auto val = nt::GetEntryValue(JavaStringRef(env, key));
+  if (!val || !val->IsRpc()) return defaultValue;
+  return ToJavaByteArray(env, val->GetRpc());
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    callRpc
+ * Signature: (Ljava/lang/String;[B)I
+ */
+JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_String_2_3B
+  (JNIEnv *env, jclass, jstring key, jbyteArray params)
+{
+  return nt::CallRpc(JavaStringRef(env, key), JavaByteRef(env, params));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    callRpc
+ * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I
+ */
+JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I
+  (JNIEnv *env, jclass, jstring key, jobject params, jint params_len)
+{
+  return nt::CallRpc(JavaStringRef(env, key),
+                     JavaByteRefBB(env, params, params_len));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    setNetworkIdentity
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setNetworkIdentity
+  (JNIEnv *env, jclass, jstring name)
+{
+  nt::SetNetworkIdentity(JavaStringRef(env, name));
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    startServer
+ * Signature: (Ljava/lang/String;Ljava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startServer
+  (JNIEnv *env, jclass, jstring persistFilename, jstring listenAddress,
+   jint port)
+{
+  nt::StartServer(JavaStringRef(env, persistFilename),
+                  JavaStringRef(env, listenAddress).c_str(), port);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    stopServer
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopServer
+  (JNIEnv *, jclass)
+{
+  nt::StopServer();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    startClient
+ * Signature: (Ljava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient
+  (JNIEnv *env, jclass, jstring serverName, jint port)
+{
+  nt::StartClient(JavaStringRef(env, serverName).c_str(), port);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    stopClient
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopClient
+  (JNIEnv *, jclass)
+{
+  nt::StopClient();
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    setUpdateRate
+ * Signature: (D)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setUpdateRate
+  (JNIEnv *, jclass, jdouble interval)
+{
+  nt::SetUpdateRate(interval);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    getConnections
+ * Signature: ()[Ledu/wpi/first/wpilibj/networktables/ConnectionInfo;
+ */
+JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getConnections
+  (JNIEnv *env, jclass)
+{
+  auto arr = nt::GetConnections();
+  jobjectArray jarr =
+      env->NewObjectArray(arr.size(), connectionInfoCls, nullptr);
+  if (!jarr) return nullptr;
+  for (size_t i = 0; i < arr.size(); ++i) {
+    JavaLocal<jobject> jelem(env, ToJavaObject(env, arr[i]));
+    env->SetObjectArrayElement(jarr, i, jelem);
+  }
+  return jarr;
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    savePersistent
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_savePersistent
+  (JNIEnv *env, jclass, jstring filename)
+{
+  const char *err = nt::SavePersistent(JavaStringRef(env, filename));
+  if (err) env->ThrowNew(persistentEx, err);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    loadPersistent
+ * Signature: (Ljava/lang/String;)[Ljava/lang/String;
+ */
+JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_loadPersistent
+  (JNIEnv *env, jclass, jstring filename)
+{
+  std::vector<std::string> warns;
+  const char *err = nt::LoadPersistent(JavaStringRef(env, filename),
+                                       [&](size_t line, const char *msg) {
+                                         std::ostringstream oss;
+                                         oss << line << ": " << msg;
+                                         warns.push_back(oss.str());
+                                       });
+  if (err) {
+    env->ThrowNew(persistentEx, err);
+    return nullptr;
+  }
+  return ToJavaStringArray(env, warns);
+}
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    now
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_now
+  (JNIEnv *, jclass)
+{
+  return nt::Now();
+}
+
+}  // extern "C"
+
+// Thread where log callbacks are actually performed.
+//
+// JNI's AttachCurrentThread() creates a Java Thread object on every
+// invocation, which is both time inefficient and causes issues with Eclipse
+// (which tries to keep a thread list up-to-date and thus gets swamped).
+//
+// Instead, this class attaches just once.  When a hardware notification
+// occurs, a condition variable wakes up this thread and this thread actually
+// makes the call into Java.
+class LoggerThreadJNI {
+ public:
+  static LoggerThreadJNI& GetInstance() {
+    ATOMIC_STATIC(LoggerThreadJNI, instance);
+    return instance;
+  }
+  LoggerThreadJNI();
+  ~LoggerThreadJNI();
+  void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
+  void Start();
+  void Stop();
+
+  void Log(unsigned int level, const char* file, unsigned int line,
+           const char* msg);
+
+ private:
+  void ThreadMain();
+
+  std::thread m_thread;
+  std::mutex m_mutex;
+  std::condition_variable m_cond;
+  std::atomic_bool m_active;
+  struct LogMessage {
+    LogMessage(unsigned int level_, const char* file_, unsigned int line_,
+               const char* msg_)
+        : level(level_), file(file_), line(line_), msg(msg_) {}
+    unsigned int level;
+    const char* file;
+    unsigned int line;
+    std::string msg;
+  };
+  std::queue<LogMessage> m_queue;
+  std::mutex m_shutdown_mutex;
+  std::condition_variable m_shutdown_cv;
+  bool m_shutdown = false;
+  jobject m_func = nullptr;
+  jmethodID m_mid;
+
+  ATOMIC_STATIC_DECL(LoggerThreadJNI)
+};
+
+ATOMIC_STATIC_INIT(LoggerThreadJNI)
+
+LoggerThreadJNI::LoggerThreadJNI() {
+  m_active = false;
+}
+
+LoggerThreadJNI::~LoggerThreadJNI() {
+  Stop();
+}
+
+void LoggerThreadJNI::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
+  std::lock_guard<std::mutex> lock(m_mutex);
+  // free global reference
+  if (m_func) env->DeleteGlobalRef(m_func);
+  // create global reference
+  m_func = env->NewGlobalRef(func);
+  m_mid = mid;
+}
+
+void LoggerThreadJNI::Start() {
+  {
+    std::lock_guard<std::mutex> lock(m_mutex);
+    if (m_active) return;
+    m_active = true;
+  }
+  {
+    std::lock_guard<std::mutex> lock(m_shutdown_mutex);
+    m_shutdown = false;
+  }
+  m_thread = std::thread(&LoggerThreadJNI::ThreadMain, this);
+}
+
+void LoggerThreadJNI::Stop() {
+  {
+    std::lock_guard<std::mutex> lock(m_mutex);
+    if (!m_active) return;
+    m_active = false;
+  }
+  m_cond.notify_one();  // wake up thread
+
+  // join threads, with timeout
+  if (m_thread.joinable()) {
+    std::unique_lock<std::mutex> lock(m_shutdown_mutex);
+    auto timeout_time =
+        std::chrono::steady_clock::now() + std::chrono::seconds(1);
+    if (m_shutdown_cv.wait_until(lock, timeout_time,
+                                 [&] { return m_shutdown; }))
+      m_thread.join();
+    else
+      m_thread.detach();  // timed out, detach it
+  }
+}
+
+void LoggerThreadJNI::Log(unsigned int level, const char *file,
+                          unsigned int line, const char *msg) {
+  std::lock_guard<std::mutex> lock(m_mutex);
+  if (!m_active) return;
+  m_queue.emplace(level, file, line, msg);
+  m_cond.notify_one();
+}
+
+void LoggerThreadJNI::ThreadMain() {
+  JNIEnv *env;
+  jint rs = jvm->AttachCurrentThread((void**)&env, NULL);
+  if (rs != JNI_OK) return;
+
+  std::unique_lock<std::mutex> lock(m_mutex);
+  while (m_active) {
+    m_cond.wait(lock, [&] { return !m_active || !m_queue.empty(); });
+    if (!m_active) break;
+    while (!m_queue.empty()) {
+      if (!m_active) break;
+      auto item = std::move(m_queue.front());
+      m_queue.pop();
+      auto func = m_func;
+      auto mid = m_mid;
+      lock.unlock();  // don't hold mutex during callback execution
+      env->CallVoidMethod(func, mid, (jint)item.level,
+                          ToJavaString(env, item.file), (jint)item.line,
+                          ToJavaString(env, item.msg));
+      if (env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+      }
+      lock.lock();
+    }
+  }
+
+  if (jvm) jvm->DetachCurrentThread();
+
+  // use condition variable to signal thread shutdown
+  {
+    std::lock_guard<std::mutex> lock(m_shutdown_mutex);
+    m_shutdown = true;
+    m_shutdown_cv.notify_one();
+  }
+}
+
+extern "C" {
+
+/*
+ * Class:     edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
+ * Method:    setLogger
+ * Signature: (Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/LoggerFunction;I)V
+ */
+JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setLogger
+  (JNIEnv *env, jclass, jobject func, jint minLevel)
+{
+  // cls is a temporary here; cannot be used within callback functor
+	jclass cls = env->GetObjectClass(func);
+  if (!cls) return;
+
+  // method ids, on the other hand, are safe to retain
+  jmethodID mid = env->GetMethodID(
+      cls, "apply", "(ILjava/lang/String;ILjava/lang/String;)V");
+  if (!mid) return;
+
+  auto& thread = LoggerThreadJNI::GetInstance();
+  thread.SetFunc(env, func, mid);
+  thread.Start();
+
+  nt::SetLogger(
+      [](unsigned int level, const char *file, unsigned int line,
+         const char *msg) {
+        LoggerThreadJNI::GetInstance().Log(level, file, line, msg);
+      },
+      minLevel);
+}
+
+}  // extern "C"
diff --git a/java/src/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java b/java/src/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java
new file mode 100644
index 0000000..d9f0e1d
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java
@@ -0,0 +1,17 @@
+package edu.wpi.first.wpilibj.networktables;
+
+public class ConnectionInfo {
+  final String remote_id;
+  final String remote_name;
+  final int remote_port;
+  final long last_update;
+  final int protocol_version;
+
+  ConnectionInfo(String remote_id, String remote_name, int remote_port, long last_update, int protocol_version) {
+    this.remote_id = remote_id;
+    this.remote_name = remote_name;
+    this.remote_port = remote_port;
+    this.last_update = last_update;
+    this.protocol_version = protocol_version;
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables/EntryInfo.java b/java/src/edu/wpi/first/wpilibj/networktables/EntryInfo.java
new file mode 100644
index 0000000..164d789
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables/EntryInfo.java
@@ -0,0 +1,15 @@
+package edu.wpi.first.wpilibj.networktables;
+
+public class EntryInfo {
+  final String name;
+  final int type;
+  final int flags;
+  final long last_change;
+
+  EntryInfo(String name, int type, int flags, long last_change) {
+    this.name = name;
+    this.type = type;
+    this.flags = flags;
+    this.last_change = last_change;
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java
new file mode 100644
index 0000000..a2d1eb6
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java
@@ -0,0 +1,958 @@
+package edu.wpi.first.wpilibj.networktables;
+
+import edu.wpi.first.wpilibj.tables.*;
+import edu.wpi.first.wpilibj.networktables2.type.*;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.*;
+
+/**
+ * A network table that knows its subtable path.
+ */
+public class NetworkTable implements ITable, IRemote {
+  /**
+   * The path separator for sub-tables and keys
+   *
+   */
+  public static final char PATH_SEPARATOR = '/';
+  /**
+   * The default port that network tables operates on
+   */
+  public static final int DEFAULT_PORT = 1735;
+
+  private static boolean client = false;
+  private static boolean running = false;
+  private static int port = DEFAULT_PORT;
+  private static String ipAddress = "";
+  private static String persistentFilename = "networktables.ini";
+
+  private synchronized static void checkInit() {
+    if (running)
+      throw new IllegalStateException(
+          "Network tables has already been initialized");
+  }
+
+  /**
+   * initializes network tables
+   */
+  public synchronized static void initialize() {
+    if (running)
+      shutdown();
+    if (client)
+      NetworkTablesJNI.startClient(ipAddress, port);
+    else
+      NetworkTablesJNI.startServer(persistentFilename, "", port);
+    running = true;
+  }
+
+  /**
+   * shuts down network tables
+   */
+  public synchronized static void shutdown() {
+    if (!running)
+      return;
+    if (client)
+      NetworkTablesJNI.stopClient();
+    else
+      NetworkTablesJNI.stopServer();
+    running = false;
+  }
+
+  /**
+   * set that network tables should be a server
+   * This must be called before initialize or getTable
+   */
+  public synchronized static void setServerMode() {
+    if (!client)
+      return;
+    checkInit();
+    client = false;
+  }
+
+  /**
+   * set that network tables should be a client
+   * This must be called before initialize or getTable
+   */
+  public synchronized static void setClientMode() {
+    if (client)
+      return;
+    checkInit();
+    client = true;
+  }
+
+  /**
+   * set the team the robot is configured for (this will set the mdns address that
+   * network tables will connect to in client mode)
+   * This must be called before initialize or getTable
+   * @param team the team number
+   */
+  public synchronized static void setTeam(int team) {
+    setIPAddress("roboRIO-" + team + "-FRC.local");
+  }
+
+  /**
+   * @param address the adress that network tables will connect to in client
+   * mode
+   */
+  public synchronized static void setIPAddress(final String address) {
+    if (ipAddress.equals(address))
+      return;
+    checkInit();
+    ipAddress = address;
+  }
+
+  /**
+   * @param aport the port number that network tables will connect to in client
+   * mode or listen to in server mode
+   */
+  public synchronized static void setPort(int aport) {
+    if (port == aport)
+      return;
+    checkInit();
+    port = aport;
+  }
+
+  /**
+   * Sets the persistent filename.
+   * @param filename the filename that the network tables server uses for
+   * automatic loading and saving of persistent values
+   */
+  public synchronized static void setPersistentFilename(final String filename) {
+    if (persistentFilename.equals(filename))
+      return;
+    checkInit();
+    persistentFilename = filename;
+  }
+
+  /**
+   * Sets the network identity.
+   * This is provided in the connection info on the remote end.
+   * @param name identity
+   */
+  public static void setNetworkIdentity(String name) {
+    NetworkTablesJNI.setNetworkIdentity(name);
+  }
+
+  public static boolean[] toNative(Boolean[] arr) {
+    boolean[] out = new boolean[arr.length];
+    for (int i = 0; i < arr.length; i++)
+      out[i] = arr[i];
+    return out;
+  }
+
+  public static double[] toNative(Double[] arr) {
+    double[] out = new double[arr.length];
+    for (int i = 0; i < arr.length; i++)
+      out[i] = arr[i];
+    return out;
+  }
+
+  public static Boolean[] fromNative(boolean[] arr) {
+    Boolean[] out = new Boolean[arr.length];
+    for (int i = 0; i < arr.length; i++)
+      out[i] = arr[i];
+    return out;
+  }
+
+  public static Double[] fromNative(double[] arr) {
+    Double[] out = new Double[arr.length];
+    for (int i = 0; i < arr.length; i++)
+      out[i] = arr[i];
+    return out;
+  }
+
+  /**
+   * Gets the table with the specified key. If the table does not exist, a new
+   *table will be created.<br>
+   * This will automatically initialize network tables if it has not been
+   *already
+   *
+   * @param key
+   *            the key name
+   * @return the network table requested
+   */
+  public synchronized static NetworkTable getTable(String key) {
+    if (!running)
+      initialize();
+    if (key.isEmpty())
+      return new NetworkTable(key);
+    return new NetworkTable(PATH_SEPARATOR + key);
+  }
+
+  private final String path;
+
+  NetworkTable(String path) {
+    this.path = path;
+  }
+  public String toString() { return "NetworkTable: " + path; }
+
+  public static ConnectionInfo[] connections() {
+    return NetworkTablesJNI.getConnections();
+  }
+
+  public boolean isConnected() {
+    ConnectionInfo[] conns = NetworkTablesJNI.getConnections();
+    return conns.length > 0;
+  }
+
+  public boolean isServer() {
+    return !client;
+  }
+
+  private class ListenerBase {
+    public int uid;
+  }
+
+  private class ConnectionListenerAdapter extends ListenerBase implements NetworkTablesJNI.ConnectionListenerFunction {
+    private final IRemote targetSource;
+    private final IRemoteConnectionListener targetListener;
+
+    public ConnectionListenerAdapter(IRemote targetSource, IRemoteConnectionListener targetListener) {
+      this.targetSource = targetSource;
+      this.targetListener = targetListener;
+    }
+
+    public void apply(int uid, boolean connected, ConnectionInfo conn) {
+      if (connected)
+        targetListener.connected(targetSource);
+      else
+        targetListener.disconnected(targetSource);
+    }
+  }
+
+  private final Hashtable<IRemoteConnectionListener,ConnectionListenerAdapter> connectionListenerMap = new Hashtable<IRemoteConnectionListener,ConnectionListenerAdapter>();
+  public synchronized void addConnectionListener(IRemoteConnectionListener listener,
+                                                 boolean immediateNotify) {
+    ConnectionListenerAdapter adapter = connectionListenerMap.get(listener);
+    if (adapter != null)
+      throw new IllegalStateException("Cannot add the same listener twice");
+    adapter = new ConnectionListenerAdapter(this, listener);
+    adapter.uid = NetworkTablesJNI.addConnectionListener(adapter, immediateNotify);
+    connectionListenerMap.put(listener, adapter);
+  }
+
+  public synchronized void removeConnectionListener(IRemoteConnectionListener listener) {
+    ConnectionListenerAdapter adapter = connectionListenerMap.get(listener);
+    if (adapter != null) {
+      NetworkTablesJNI.removeConnectionListener(adapter.uid);
+      connectionListenerMap.remove(listener);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void addTableListener(ITableListener listener) {
+    addTableListenerEx(listener, NOTIFY_NEW | NOTIFY_UPDATE);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void addTableListener(ITableListener listener,
+                               boolean immediateNotify) {
+    int flags = NOTIFY_NEW | NOTIFY_UPDATE;
+    if (immediateNotify)
+      flags |= NOTIFY_IMMEDIATE;
+    addTableListenerEx(listener, flags);
+  }
+
+  private class TableListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction {
+    private final int prefixLen;
+    private final ITable targetSource;
+    private final ITableListener targetListener;
+
+    public TableListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) {
+      this.prefixLen = prefixLen;
+      this.targetSource = targetSource;
+      this.targetListener = targetListener;
+    }
+
+    public void apply(int uid, String key, Object value, int flags) {
+      String relativeKey = key.substring(prefixLen);
+      if (relativeKey.indexOf(PATH_SEPARATOR) != -1)
+        return;
+      targetListener.valueChangedEx(targetSource, relativeKey, value, flags);
+    }
+  }
+
+  private final Hashtable<ITableListener,List<ListenerBase>> listenerMap = new Hashtable<ITableListener,List<ListenerBase>>();
+  public synchronized void addTableListenerEx(ITableListener listener,
+                                              int flags) {
+    List<ListenerBase> adapters = listenerMap.get(listener);
+    if (adapters == null) {
+      adapters = new ArrayList<ListenerBase>();
+      listenerMap.put(listener, adapters);
+    }
+    TableListenerAdapter adapter =
+        new TableListenerAdapter(path.length() + 1, this, listener);
+    adapter.uid = NetworkTablesJNI.addEntryListener(path + PATH_SEPARATOR, adapter, flags);
+    adapters.add(adapter);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void addTableListener(String key, ITableListener listener,
+                               boolean immediateNotify) {
+    int flags = NOTIFY_NEW | NOTIFY_UPDATE;
+    if (immediateNotify)
+      flags |= NOTIFY_IMMEDIATE;
+    addTableListenerEx(key, listener, flags);
+  }
+
+  private class KeyListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction {
+    private final String relativeKey;
+    private final String fullKey;
+    private final ITable targetSource;
+    private final ITableListener targetListener;
+
+    public KeyListenerAdapter(String relativeKey, String fullKey, ITable targetSource, ITableListener targetListener) {
+      this.relativeKey = relativeKey;
+      this.fullKey = fullKey;
+      this.targetSource = targetSource;
+      this.targetListener = targetListener;
+    }
+
+    public void apply(int uid, String key, Object value, int flags) {
+      if (!key.equals(fullKey))
+        return;
+      targetListener.valueChangedEx(targetSource, relativeKey, value, flags);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public synchronized void addTableListenerEx(String key,
+                                              ITableListener listener,
+                                              int flags) {
+    List<ListenerBase> adapters = listenerMap.get(listener);
+    if (adapters == null) {
+      adapters = new ArrayList<ListenerBase>();
+      listenerMap.put(listener, adapters);
+    }
+    String fullKey = path + PATH_SEPARATOR + key;
+    KeyListenerAdapter adapter =
+        new KeyListenerAdapter(key, fullKey, this, listener);
+    adapter.uid = NetworkTablesJNI.addEntryListener(fullKey, adapter, flags);
+    adapters.add(adapter);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void addSubTableListener(final ITableListener listener) {
+    addSubTableListener(listener, false);
+  }
+
+  private class SubListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction {
+    private final int prefixLen;
+    private final ITable targetSource;
+    private final ITableListener targetListener;
+    private final Set<String> notifiedTables = new HashSet<String>();
+
+    public SubListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) {
+      this.prefixLen = prefixLen;
+      this.targetSource = targetSource;
+      this.targetListener = targetListener;
+    }
+
+    public void apply(int uid, String key, Object value, int flags) {
+      String relativeKey = key.substring(prefixLen);
+      int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
+      if (endSubTable == -1)
+        return;
+      String subTableKey = relativeKey.substring(0, endSubTable);
+      if (notifiedTables.contains(subTableKey))
+        return;
+      notifiedTables.add(subTableKey);
+      targetListener.valueChangedEx(targetSource, subTableKey, targetSource.getSubTable(subTableKey), flags);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public synchronized void addSubTableListener(final ITableListener listener,
+                                               boolean localNotify) {
+    List<ListenerBase> adapters = listenerMap.get(listener);
+    if (adapters == null) {
+      adapters = new ArrayList<ListenerBase>();
+      listenerMap.put(listener, adapters);
+    }
+    SubListenerAdapter adapter =
+        new SubListenerAdapter(path.length() + 1, this, listener);
+    int flags = NOTIFY_NEW | NOTIFY_IMMEDIATE;
+    if (localNotify)
+      flags |= NOTIFY_LOCAL;
+    adapter.uid = NetworkTablesJNI.addEntryListener(path + PATH_SEPARATOR, adapter, flags);
+    adapters.add(adapter);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public synchronized void removeTableListener(ITableListener listener) {
+    List<ListenerBase> adapters = listenerMap.get(listener);
+    if (adapters != null) {
+      for (int i = 0; i < adapters.size(); ++i)
+        NetworkTablesJNI.removeEntryListener(adapters.get(i).uid);
+      adapters.clear();
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ITable getSubTable(String key) {
+    return new NetworkTable(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean containsKey(String key) {
+    return NetworkTablesJNI.containsKey(path + PATH_SEPARATOR + key);
+  }
+
+  public boolean containsSubTable(String key) {
+    EntryInfo[] entries = NetworkTablesJNI.getEntries(path + PATH_SEPARATOR + key + PATH_SEPARATOR, 0);
+    return entries.length != 0;
+  }
+
+  /**
+   * @param types bitmask of types; 0 is treated as a "don't care".
+   * @return keys currently in the table
+   */
+  public Set<String> getKeys(int types) {
+    Set<String> keys = new HashSet<String>();
+    int prefixLen = path.length() + 1;
+    for (EntryInfo entry : NetworkTablesJNI.getEntries(path + PATH_SEPARATOR, types)) {
+      String relativeKey = entry.name.substring(prefixLen);
+      if (relativeKey.indexOf(PATH_SEPARATOR) != -1)
+        continue;
+      keys.add(relativeKey);
+    }
+    return keys;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Set<String> getKeys() {
+    return getKeys(0);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Set<String> getSubTables() {
+    Set<String> keys = new HashSet<String>();
+    int prefixLen = path.length() + 1;
+    for (EntryInfo entry : NetworkTablesJNI.getEntries(path + PATH_SEPARATOR, 0)) {
+      String relativeKey = entry.name.substring(prefixLen);
+      int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
+      if (endSubTable == -1)
+        continue;
+      keys.add(relativeKey.substring(0, endSubTable));
+    }
+    return keys;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putNumber(String key, double value) {
+    return NetworkTablesJNI.putDouble(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getNumber(String, double)}.
+   */
+  @Override
+  @Deprecated
+  public double getNumber(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getDouble(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public double getNumber(String key, double defaultValue) {
+    return NetworkTablesJNI.getDouble(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putString(String key, String value) {
+    return NetworkTablesJNI.putString(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getString(String, String)}.
+   */
+  @Override
+  @Deprecated
+  public String getString(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getString(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getString(String key, String defaultValue) {
+    return NetworkTablesJNI.getString(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putBoolean(String key, boolean value) {
+    return NetworkTablesJNI.putBoolean(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getBoolean(String, boolean)}.
+   */
+  @Override
+  @Deprecated
+  public boolean getBoolean(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getBoolean(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean getBoolean(String key, boolean defaultValue) {
+    return NetworkTablesJNI.getBoolean(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putBooleanArray(String key, boolean[] value) {
+    return NetworkTablesJNI.putBooleanArray(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putBooleanArray(String key, Boolean[] value) {
+    return putBooleanArray(key, toNative(value));
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getBooleanArray(String, boolean[])}.
+   */
+  @Override
+  @Deprecated
+  public boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getBooleanArray(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean[] getBooleanArray(String key, boolean[] defaultValue) {
+    return NetworkTablesJNI.getBooleanArray(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Boolean[] getBooleanArray(String key, Boolean[] defaultValue) {
+    try {
+      return fromNative(getBooleanArray(key));
+    } catch (TableKeyNotDefinedException e) {
+      return defaultValue;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putNumberArray(String key, double[] value) {
+    return NetworkTablesJNI.putDoubleArray(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putNumberArray(String key, Double[] value) {
+    return putNumberArray(key, toNative(value));
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getNumberArray(String, double[])}.
+   */
+  @Override
+  @Deprecated
+  public double[] getNumberArray(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getDoubleArray(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public double[] getNumberArray(String key, double[] defaultValue) {
+    return NetworkTablesJNI.getDoubleArray(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Double[] getNumberArray(String key, Double[] defaultValue) {
+    try {
+      return fromNative(getNumberArray(key));
+    } catch (TableKeyNotDefinedException e) {
+      return defaultValue;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putStringArray(String key, String[] value) {
+    return NetworkTablesJNI.putStringArray(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getStringArray(String, String[])}.
+   */
+  @Override
+  @Deprecated
+  public String[] getStringArray(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getStringArray(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String[] getStringArray(String key, String[] defaultValue) {
+    return NetworkTablesJNI.getStringArray(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putRaw(String key, byte[] value) {
+    return NetworkTablesJNI.putRaw(path + PATH_SEPARATOR + key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putRaw(String key, ByteBuffer value, int len) {
+    if (!value.isDirect())
+      throw new IllegalArgumentException("must be a direct buffer");
+    if (value.capacity() < len)
+      throw new IllegalArgumentException("buffer is too small, must be at least " + len);
+    return NetworkTablesJNI.putRaw(path + PATH_SEPARATOR + key, value, len);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getRaw(String, byte[])}.
+   */
+  @Override
+  @Deprecated
+  public byte[] getRaw(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getRaw(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public byte[] getRaw(String key, byte[] defaultValue) {
+    return NetworkTablesJNI.getRaw(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean putValue(String key, Object value) throws IllegalArgumentException {
+    if (value instanceof Boolean)
+      return NetworkTablesJNI.putBoolean(path + PATH_SEPARATOR + key, ((Boolean)value).booleanValue());
+    else if (value instanceof Double)
+      return NetworkTablesJNI.putDouble(path + PATH_SEPARATOR + key, ((Double)value).doubleValue());
+    else if (value instanceof String)
+      return NetworkTablesJNI.putString(path + PATH_SEPARATOR + key, (String)value);
+    else if (value instanceof byte[])
+      return NetworkTablesJNI.putRaw(path + PATH_SEPARATOR + key, (byte[])value);
+    else if (value instanceof boolean[])
+      return NetworkTablesJNI.putBooleanArray(path + PATH_SEPARATOR + key, (boolean[])value);
+    else if (value instanceof double[])
+      return NetworkTablesJNI.putDoubleArray(path + PATH_SEPARATOR + key, (double[])value);
+    else if (value instanceof Boolean[])
+      return NetworkTablesJNI.putBooleanArray(path + PATH_SEPARATOR + key, toNative((Boolean[])value));
+    else if (value instanceof Double[])
+      return NetworkTablesJNI.putDoubleArray(path + PATH_SEPARATOR + key, toNative((Double[])value));
+    else if (value instanceof String[])
+      return NetworkTablesJNI.putStringArray(path + PATH_SEPARATOR + key, (String[])value);
+    else if (value instanceof BooleanArray)
+      return NetworkTablesJNI.putBooleanArray(path + PATH_SEPARATOR + key, toNative((Boolean[])((ArrayData)value).getDataArray()));
+    else if (value instanceof NumberArray)
+      return NetworkTablesJNI.putDoubleArray(path + PATH_SEPARATOR + key, toNative((Double[])((ArrayData)value).getDataArray()));
+    else if (value instanceof StringArray)
+      return NetworkTablesJNI.putStringArray(path + PATH_SEPARATOR + key, (String[])((ArrayData)value).getDataArray());
+    else
+      throw new IllegalArgumentException(key);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use get*Array functions instead.
+   */
+  @Override
+  @Deprecated
+  public void retrieveValue(String key, Object externalData) throws TableKeyNotDefinedException {
+    Object value = getValue(key);
+    if (value instanceof boolean[] && externalData instanceof BooleanArray)
+      ((ArrayData)externalData).setDataArray(fromNative((boolean[])value));
+    else if (value instanceof double[] && externalData instanceof NumberArray)
+      ((ArrayData)externalData).setDataArray(fromNative((double[])value));
+    else if (value instanceof String[] && externalData instanceof StringArray)
+      ((ArrayData)externalData).setDataArray((String[])value);
+    else
+      throw new TableKeyNotDefinedException(key);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getValue(String, Object)}.
+   */
+  @Override
+  @Deprecated
+  public Object getValue(String key) throws TableKeyNotDefinedException {
+    return NetworkTablesJNI.getValue(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Object getValue(String key, Object defaultValue) {
+    return NetworkTablesJNI.getValue(path + PATH_SEPARATOR + key, defaultValue);
+  }
+
+  /** The persistent flag value. */
+  public static final int PERSISTENT = 1;
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void setPersistent(String key) {
+    setFlags(key, PERSISTENT);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void clearPersistent(String key) {
+    clearFlags(key, PERSISTENT);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isPersistent(String key) {
+    return (getFlags(key) & PERSISTENT) != 0;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void setFlags(String key, int flags) {
+    NetworkTablesJNI.setEntryFlags(path + PATH_SEPARATOR + key, getFlags(key) | flags);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void clearFlags(String key, int flags) {
+    NetworkTablesJNI.setEntryFlags(path + PATH_SEPARATOR + key, getFlags(key) & ~flags);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int getFlags(String key) {
+    return NetworkTablesJNI.getEntryFlags(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void delete(String key) {
+    NetworkTablesJNI.deleteEntry(path + PATH_SEPARATOR + key);
+  }
+
+  /**
+   * Deletes ALL keys in ALL subtables.  Use with caution!
+   */
+  public static void globalDeleteAll() {
+    NetworkTablesJNI.deleteAllEntries();
+  }
+
+  /**
+   * Flushes all updated values immediately to the network.
+   * Note: This is rate-limited to protect the network from flooding.
+   * This is primarily useful for synchronizing network updates with
+   * user code.
+   */
+  public static void flush() {
+    NetworkTablesJNI.flush();
+  }
+
+  /**
+   * Set the periodic update rate.
+   *
+   * @param interval update interval in seconds (range 0.1 to 1.0)
+   */
+  public static void setUpdateRate(double interval) {
+    NetworkTablesJNI.setUpdateRate(interval);
+  }
+
+  /**
+   * Saves persistent keys to a file.  The server does this automatically.
+   *
+   * @param filename file name
+   * @throws PersistentException if error saving file
+   */
+  public static void savePersistent(String filename) throws PersistentException {
+    NetworkTablesJNI.savePersistent(filename);
+  }
+
+  /**
+   * Loads persistent keys from a file.  The server does this automatically.
+   *
+   * @param filename file name
+   * @return List of warnings (errors result in an exception instead)
+   * @throws PersistentException if error reading file
+   */
+  public static String[] loadPersistent(String filename) throws PersistentException {
+    return NetworkTablesJNI.loadPersistent(filename);
+  }
+
+  /*
+   * Deprecated Methods
+   */
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use {@link #putNumber(String, double)} instead.
+   */
+  @Override
+  @Deprecated
+  public boolean putInt(String key, int value) {
+    return putNumber(key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Override
+  @Deprecated
+  public int getInt(String key) throws TableKeyNotDefinedException {
+    return (int)getNumber(key);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Override
+  @Deprecated
+  public int getInt(String key, int defaultValue) throws TableKeyNotDefinedException {
+    try {
+      return (int)getNumber(key);
+    } catch (NoSuchElementException ex) {
+      return defaultValue;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use {@link #putNumber(String, double)} instead.
+   */
+  @Override
+  @Deprecated
+  public boolean putDouble(String key, double value) {
+    return putNumber(key, value);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Override
+  @Deprecated
+  public double getDouble(String key) throws TableKeyNotDefinedException {
+    return getNumber(key);
+  }
+
+  /**
+   * {@inheritDoc}
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Override
+  @Deprecated
+  public double getDouble(String key, double defaultValue) {
+    return getNumber(key, defaultValue);
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined.java
new file mode 100644
index 0000000..d654cfc
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined.java
@@ -0,0 +1,22 @@
+package edu.wpi.first.wpilibj.networktables;
+
+import edu.wpi.first.wpilibj.tables.TableKeyNotDefinedException;
+
+/**
+ * An exception throw when the lookup a a key-value fails in a {@link NetworkTable}
+ * 
+ * @deprecated to provide backwards compatability for new api
+ * 
+ * @author Mitchell
+ *
+ */
+public class NetworkTableKeyNotDefined extends TableKeyNotDefinedException {
+
+	/**
+	 * @param key the key that was not defined in the table
+	 */
+	public NetworkTableKeyNotDefined(String key) {
+		super(key);
+	}
+
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java
new file mode 100644
index 0000000..810d7f8
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java
@@ -0,0 +1,155 @@
+package edu.wpi.first.wpilibj.networktables;
+
+import edu.wpi.first.wpilibj.tables.*;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class NetworkTablesJNI {
+  static boolean libraryLoaded = false;
+  static File jniLibrary = null;
+  static {
+    if (!libraryLoaded) {
+      try {
+        String osname = System.getProperty("os.name");
+        String resname;
+        if (osname.startsWith("Windows"))
+          resname = "/Windows/" + System.getProperty("os.arch") + "/";
+        else
+          resname = "/" + osname + "/" + System.getProperty("os.arch") + "/";
+        System.out.println("platform: " + resname);
+        if (osname.startsWith("Windows"))
+          resname += "ntcore.dll";
+        else if (osname.startsWith("Mac"))
+          resname += "libntcore.dylib";
+        else
+          resname += "libntcore.so";
+        InputStream is = NetworkTablesJNI.class.getResourceAsStream(resname);
+        if (is != null) {
+          // create temporary file
+          if (System.getProperty("os.name").startsWith("Windows"))
+            jniLibrary = File.createTempFile("NetworkTablesJNI", ".dll");
+          else if (System.getProperty("os.name").startsWith("Mac"))
+            jniLibrary = File.createTempFile("libNetworkTablesJNI", ".dylib");
+          else
+            jniLibrary = File.createTempFile("libNetworkTablesJNI", ".so");
+          // flag for delete on exit
+          jniLibrary.deleteOnExit();
+          OutputStream os = new FileOutputStream(jniLibrary);
+
+          byte[] buffer = new byte[1024];
+          int readBytes;
+          try {
+            while ((readBytes = is.read(buffer)) != -1) {
+              os.write(buffer, 0, readBytes);
+            }
+          } finally {
+            os.close();
+            is.close();
+          }
+
+          System.load(jniLibrary.getAbsolutePath());
+        } else {
+          System.loadLibrary("ntcore");
+        }
+      } catch (IOException ex) {
+        ex.printStackTrace();
+        System.exit(1);
+      }
+      libraryLoaded = true;
+    }
+  }
+
+  public static native boolean containsKey(String key);
+  public static native int getType(String key);
+
+  public static native boolean putBoolean(String key, boolean value);
+  public static native boolean putDouble(String key, double value);
+  public static native boolean putString(String key, String value);
+  public static native boolean putRaw(String key, byte[] value);
+  public static native boolean putRaw(String key, ByteBuffer value, int len);
+  public static native boolean putBooleanArray(String key, boolean[] value);
+  public static native boolean putDoubleArray(String key, double[] value);
+  public static native boolean putStringArray(String key, String[] value);
+
+  public static native void forcePutBoolean(String key, boolean value);
+  public static native void forcePutDouble(String key, double value);
+  public static native void forcePutString(String key, String value);
+  public static native void forcePutRaw(String key, byte[] value);
+  public static native void forcePutRaw(String key, ByteBuffer value, int len);
+  public static native void forcePutBooleanArray(String key, boolean[] value);
+  public static native void forcePutDoubleArray(String key, double[] value);
+  public static native void forcePutStringArray(String key, String[] value);
+
+  public static native Object getValue(String key) throws TableKeyNotDefinedException;
+  public static native boolean getBoolean(String key) throws TableKeyNotDefinedException;
+  public static native double getDouble(String key) throws TableKeyNotDefinedException;
+  public static native String getString(String key) throws TableKeyNotDefinedException;
+  public static native byte[] getRaw(String key) throws TableKeyNotDefinedException;
+  public static native boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException;
+  public static native double[] getDoubleArray(String key) throws TableKeyNotDefinedException;
+  public static native String[] getStringArray(String key) throws TableKeyNotDefinedException;
+
+  public static native Object getValue(String key, Object defaultValue);
+  public static native boolean getBoolean(String key, boolean defaultValue);
+  public static native double getDouble(String key, double defaultValue);
+  public static native String getString(String key, String defaultValue);
+  public static native byte[] getRaw(String key, byte[] defaultValue);
+  public static native boolean[] getBooleanArray(String key, boolean[] defaultValue);
+  public static native double[] getDoubleArray(String key, double[] defaultValue);
+  public static native String[] getStringArray(String key, String[] defaultValue);
+
+  public static native void setEntryFlags(String key, int flags);
+  public static native int getEntryFlags(String key);
+
+  public static native void deleteEntry(String key);
+  public static native void deleteAllEntries();
+
+  public static native EntryInfo[] getEntries(String prefix, int types);
+
+  public static native void flush();
+
+  public interface EntryListenerFunction {
+    void apply(int uid, String key, Object value, int flags);
+  }
+  public static native int addEntryListener(String prefix, EntryListenerFunction listener, int flags);
+  public static native void removeEntryListener(int entryListenerUid);
+
+  public interface ConnectionListenerFunction {
+    void apply(int uid, boolean connected, ConnectionInfo conn);
+  }
+  public static native int addConnectionListener(ConnectionListenerFunction listener, boolean immediateNotify);
+  public static native void removeConnectionListener(int connListenerUid);
+
+  // public static native void createRpc(String key, byte[] def, IRpc rpc);
+  // public static native void createRpc(String key, ByteBuffer def, int def_len, IRpc rpc);
+  public static native byte[] getRpc(String key) throws TableKeyNotDefinedException;
+  public static native byte[] getRpc(String key, byte[] defaultValue);
+  public static native int callRpc(String key, byte[] params);
+  public static native int callRpc(String key, ByteBuffer params, int params_len);
+  // public static native byte[] getRpcResultBlocking(int callUid);
+  // public static native byte[] getRpcResultNonblocking(int callUid) throws RpcNoResponseException;
+
+  public static native void setNetworkIdentity(String name);
+  public static native void startServer(String persistFilename, String listenAddress, int port);
+  public static native void stopServer();
+  public static native void startClient(String serverName, int port);
+  public static native void stopClient();
+  public static native void setUpdateRate(double interval);
+
+  public static native ConnectionInfo[] getConnections();
+
+  public static native void savePersistent(String filename) throws PersistentException;
+  public static native String[] loadPersistent(String filename) throws PersistentException;  // returns warnings
+
+  public static native long now();
+
+  public interface LoggerFunction {
+    void apply(int level, String file, int line, String msg);
+  }
+  public static native void setLogger(LoggerFunction func, int minLevel);
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables/PersistentException.java b/java/src/edu/wpi/first/wpilibj/networktables/PersistentException.java
new file mode 100644
index 0000000..8d521fb
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables/PersistentException.java
@@ -0,0 +1,18 @@
+package edu.wpi.first.wpilibj.networktables;
+
+import java.io.IOException;
+
+/**
+ * An exception thrown when persistent load/save fails in a {@link NetworkTable}
+ * 
+ */
+public class PersistentException extends IOException {
+
+	/**
+	 * @param message The error message
+	 */
+	public PersistentException(String message) {
+		super(message);
+	}
+
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables2/type/ArrayData.java b/java/src/edu/wpi/first/wpilibj/networktables2/type/ArrayData.java
new file mode 100644
index 0000000..e05f3a0
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables2/type/ArrayData.java
@@ -0,0 +1,49 @@
+package edu.wpi.first.wpilibj.networktables2.type;
+
+/**
+ * @deprecated Use ArrayList instead.
+ */
+public class ArrayData {
+  private Object[] data = new Object[0];
+
+  protected Object getAsObject(int index) {
+    return data[index];
+  }
+  protected void _set(int index, Object value) {
+    data[index] = value;
+  }
+  protected void _add(Object value) {
+    setSize(size() + 1);
+    data[size() - 1] = value;
+  }
+  public void remove(int index) {
+    if (index < 0 || index >= size())
+      throw new IndexOutOfBoundsException();
+    if (index < size() - 1)
+      System.arraycopy(data, index + 1, data, index, size() - index - 1);
+    setSize(size() - 1);
+  }
+  public void setSize(int size) {
+    if (size == data.length)
+      return;
+    Object[] newArray = new Object[size];
+    if (size < data.length)
+      System.arraycopy(data, 0, newArray, 0, size);
+    else {
+      System.arraycopy(data, 0, newArray, 0, data.length);
+      for (int i = data.length; i < newArray.length; ++i)
+        newArray[i] = null;
+    }
+    data = newArray;
+  }
+  public int size() {
+    return data.length;
+  }
+
+  public Object[] getDataArray() {
+    return data;
+  }
+  public void setDataArray(Object[] value) {
+    data = value;
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables2/type/BooleanArray.java b/java/src/edu/wpi/first/wpilibj/networktables2/type/BooleanArray.java
new file mode 100644
index 0000000..f2c78e6
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables2/type/BooleanArray.java
@@ -0,0 +1,16 @@
+package edu.wpi.first.wpilibj.networktables2.type;
+
+/**
+ * @deprecated Use {@literal ArrayList<Boolean>} instead.
+ */
+public class BooleanArray extends ArrayData {
+  public boolean get(int index) {
+    return ((Boolean)getAsObject(index)).booleanValue();
+  }
+  public void set(int index, boolean value) {
+    _set(index, value?Boolean.TRUE:Boolean.FALSE);
+  }
+  public void add(boolean value) {
+    _add(value?Boolean.TRUE:Boolean.FALSE);
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables2/type/NumberArray.java b/java/src/edu/wpi/first/wpilibj/networktables2/type/NumberArray.java
new file mode 100644
index 0000000..1066159
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables2/type/NumberArray.java
@@ -0,0 +1,16 @@
+package edu.wpi.first.wpilibj.networktables2.type;
+
+/**
+ * @deprecated Use {@literal ArrayList<Double>} instead.
+ */
+public class NumberArray extends ArrayData {
+  public double get(int index) {
+    return ((Double)getAsObject(index)).doubleValue();
+  }
+  public void set(int index, double value) {
+    _set(index, new Double(value));
+  }
+  public void add(double value) {
+    _add(new Double(value));
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/networktables2/type/StringArray.java b/java/src/edu/wpi/first/wpilibj/networktables2/type/StringArray.java
new file mode 100644
index 0000000..5eea0d3
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/networktables2/type/StringArray.java
@@ -0,0 +1,16 @@
+package edu.wpi.first.wpilibj.networktables2.type;
+
+/**
+ * @deprecated Use {@literal ArrayList<String>} instead.
+ */
+public class StringArray extends ArrayData {
+  public String get(int index) {
+    return ((String)getAsObject(index));
+  }
+  public void set(int index, String value) {
+    _set(index, value);
+  }
+  public void add(String value) {
+    _add(value);
+  }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/tables/IRemote.java b/java/src/edu/wpi/first/wpilibj/tables/IRemote.java
new file mode 100644
index 0000000..a79f740
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/tables/IRemote.java
@@ -0,0 +1,37 @@
+package edu.wpi.first.wpilibj.tables;
+
+
+/**
+ * Represents an object that has a remote connection
+ * 
+ * @author Mitchell
+ *
+ */
+public interface IRemote {
+	/**
+	 * Register an object to listen for connection and disconnection events
+	 * 
+	 * @param listener the listener to be register
+	 * @param immediateNotify if the listener object should be notified of the current connection state
+	 */
+	public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify);
+
+	/**
+	 * Unregister a listener from connection events
+	 * 
+	 * @param listener the listener to be unregistered
+	 */
+	public void removeConnectionListener(IRemoteConnectionListener listener);
+	
+	/**
+	 * Get the current state of the objects connection
+	 * @return the current connection state
+	 */
+    public boolean isConnected();
+	
+	/**
+	 * If the object is acting as a server
+	 * @return if the object is a server
+	 */
+    public boolean isServer();
+}
diff --git a/java/src/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java b/java/src/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java
new file mode 100644
index 0000000..cab9b1c
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java
@@ -0,0 +1,20 @@
+package edu.wpi.first.wpilibj.tables;
+
+/**
+ * A listener that listens for connection changes in a {@link IRemote} object
+ * 
+ * @author Mitchell
+ *
+ */
+public interface IRemoteConnectionListener {
+	/**
+	 * Called when an IRemote is connected
+	 * @param remote the object that connected
+	 */
+	public void connected(IRemote remote);
+	/**
+	 * Called when an IRemote is disconnected
+	 * @param remote the object that disconnected
+	 */
+	public void disconnected(IRemote remote);
+}
diff --git a/java/src/edu/wpi/first/wpilibj/tables/ITable.java b/java/src/edu/wpi/first/wpilibj/tables/ITable.java
new file mode 100644
index 0000000..81d12be
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/tables/ITable.java
@@ -0,0 +1,547 @@
+package edu.wpi.first.wpilibj.tables;
+
+import java.nio.ByteBuffer;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+
+/**
+ * A table whose values can be read and written to
+ */
+public interface ITable {
+
+  /**
+   * Checks the table and tells if it contains the specified key
+   *
+   * @param key the key to search for
+   * @return true if the table as a value assigned to the given key
+   */
+  public boolean containsKey(String key);
+
+  /**
+   * @param key the key to search for
+   * @return true if there is a subtable with the key which contains at least
+   * one key/subtable of its own
+   */
+  public boolean containsSubTable(String key);
+
+  /**
+   * Returns the table at the specified key. If there is no table at the
+   * specified key, it will create a new table
+   *
+   * @param key the name of the table relative to this one
+   * @return a sub table relative to this one
+   */
+  public ITable getSubTable(String key);
+
+  /**
+   * @param types bitmask of types; 0 is treated as a "don't care".
+   * @return keys currently in the table
+   */
+  public Set<String> getKeys(int types);
+
+  /**
+   * @return keys currently in the table
+   */
+  public Set<String> getKeys();
+
+  /**
+   * @return subtables currently in the table
+   */
+  public Set<String> getSubTables();
+
+  /**
+   * Makes a key's value persistent through program restarts.
+   * The key cannot be null.
+   *
+   * @param key the key name
+   */
+  public void setPersistent(String key);
+
+  /**
+   * Stop making a key's value persistent through program restarts.
+   * The key cannot be null.
+   *
+   * @param key the key name
+   */
+  public void clearPersistent(String key);
+
+  /**
+   * Returns whether the value is persistent through program restarts.
+   * The key cannot be null.
+   *
+   * @param key the key name
+   * @return True if the value is persistent.
+   */
+  public boolean isPersistent(String key);
+
+  /**
+   * Sets flags on the specified key in this table. The key can
+   * not be null.
+   *
+   * @param key the key name
+   * @param flags the flags to set (bitmask)
+   */
+  public void setFlags(String key, int flags);
+
+  /**
+   * Clears flags on the specified key in this table. The key can
+   * not be null.
+   *
+   * @param key the key name
+   * @param flags the flags to clear (bitmask)
+   */
+  public void clearFlags(String key, int flags);
+
+  /**
+   * Returns the flags for the specified key.
+   *
+   * @param key the key name
+   * @return the flags, or 0 if the key is not defined
+   */
+  public int getFlags(String key);
+
+  /**
+   * Deletes the specified key in this table. The key can
+   * not be null.
+   *
+   * @param key the key name
+   */
+  public void delete(String key);
+
+  /**
+   * Gets the value associated with a key as an object. If the key does not
+   * exist, it will return the default value
+   * NOTE: If the value is a double, it will return a Double object,
+   * not a primitive.  To get the primitive, use
+   * {@link #getDouble(String, double)}.
+   * @param key the key of the value to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getValue(String, Object)}.
+   */
+  @Deprecated
+  public Object getValue(String key) throws TableKeyNotDefinedException;
+
+  /**
+   * Gets the value associated with a key as an object.
+   * NOTE: If the value is a double, it will return a Double object,
+   * not a primitive.  To get the primitive, use
+   * {@link #getDouble(String, double)}.
+   * @param key the key of the value to look up
+   * @param defaultValue the default value if the key is null
+   * @return the value associated with the given key
+   */
+  public Object getValue(String key, Object defaultValue);
+
+  /**
+   * Put a value in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   * @throws IllegalArgumentException when the value is not supported by the
+   * table
+   */
+  public boolean putValue(String key, Object value)
+      throws IllegalArgumentException;
+
+  /**
+   * Retrieve an array data type from the table.
+   * @param key the key to be assigned to
+   * @param externalValue the array data type to retreive into
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated Use get*Array functions instead.
+   */
+  @Deprecated
+  public void retrieveValue(String key, Object externalValue) throws TableKeyNotDefinedException;
+
+  /**
+   * Put a number in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putNumber(String key, double value);
+  /**
+   * Returns the number the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getNumber(String, double)}.
+   */
+  @Deprecated
+  public double getNumber(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the number the key maps to. If the key does not exist or is of
+   * different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public double getNumber(String key, double defaultValue);
+
+  /**
+   * Put a string in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putString(String key, String value);
+  /**
+   * Returns the string the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getString(String, String)}.
+   */
+  @Deprecated
+  public String getString(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the string the key maps to. If the key does not exist or is of
+   * different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public String getString(String key, String defaultValue);
+
+  /**
+   * Put a boolean in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putBoolean(String key, boolean value);
+  /**
+   * Returns the boolean the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getBoolean(String, boolean)}.
+   */
+  @Deprecated
+  public boolean getBoolean(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the boolean the key maps to. If the key does not exist or is of
+   * different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public boolean getBoolean(String key, boolean defaultValue);
+
+  /**
+   * Put a boolean array in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putBooleanArray(String key, boolean[] value);
+  /**
+   * Put a boolean array in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putBooleanArray(String key, Boolean[] value);
+  /**
+   * Returns the boolean array the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getBooleanArray(String, boolean[])}.
+   */
+  @Deprecated
+  public boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the boolean array the key maps to. If the key does not exist or is
+   * of different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public boolean[] getBooleanArray(String key, boolean[] defaultValue);
+  /**
+   * Returns the boolean array the key maps to. If the key does not exist or is
+   * of different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public Boolean[] getBooleanArray(String key, Boolean[] defaultValue);
+
+  /**
+   * Put a number array in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putNumberArray(String key, double[] value);
+  /**
+   * Put a number array in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putNumberArray(String key, Double[] value);
+  /**
+   * Returns the number array the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getNumberArray(String, double[])}.
+   */
+  @Deprecated
+  public double[] getNumberArray(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the number array the key maps to. If the key does not exist or is
+   * of different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public double[] getNumberArray(String key, double[] defaultValue);
+  /**
+   * Returns the number array the key maps to. If the key does not exist or is
+   * of different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public Double[] getNumberArray(String key, Double[] defaultValue);
+
+  /**
+   * Put a string array in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putStringArray(String key, String[] value);
+  /**
+   * Returns the string array the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getStringArray(String, String[])}.
+   */
+  @Deprecated
+  public String[] getStringArray(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the string array the key maps to. If the key does not exist or is
+   * of different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public String[] getStringArray(String key, String[] defaultValue);
+
+  /**
+   * Put a raw value (byte array) in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putRaw(String key, byte[] value);
+  /**
+   * Put a raw value (bytes from a byte buffer) in the table
+   * @param key the key to be assigned to
+   * @param value the value that will be assigned
+   * @param len the length of the value
+   * @return False if the table key already exists with a different type
+   */
+  public boolean putRaw(String key, ByteBuffer value, int len);
+  /**
+   * Returns the raw value (byte array) the key maps to.
+   * @param key the key to look up
+   * @return the value associated with the given key
+   * @throws TableKeyNotDefinedException if there is no value associated with
+   * the given key
+   * @deprecated This exception-raising method has been replaced by the
+   * default-taking method {@link #getRaw(String, byte[])}.
+   */
+  @Deprecated
+  public byte[] getRaw(String key) throws TableKeyNotDefinedException;
+  /**
+   * Returns the raw value (byte array) the key maps to. If the key does not
+   * exist or is of different type, it will return the default value.
+   * @param key the key to look up
+   * @param defaultValue the value to be returned if no value is found
+   * @return the value associated with the given key or the given default value
+   * if there is no value associated with the key
+   */
+  public byte[] getRaw(String key, byte[] defaultValue);
+
+  /** Notifier flag values. */
+  public static final int NOTIFY_IMMEDIATE = 0x01;
+  public static final int NOTIFY_LOCAL = 0x02;
+  public static final int NOTIFY_NEW = 0x04;
+  public static final int NOTIFY_DELETE = 0x08;
+  public static final int NOTIFY_UPDATE = 0x10;
+  public static final int NOTIFY_FLAGS = 0x20;
+
+  /**
+   * Add a listener for changes to the table
+   * @param listener the listener to add
+   */
+  public void addTableListener(ITableListener listener);
+  /**
+   * Add a listener for changes to the table
+   * @param listener the listener to add
+   * @param immediateNotify if true then this listener will be notified of all
+   * current entries (marked as new)
+   */
+  public void addTableListener(ITableListener listener,
+                               boolean immediateNotify);
+  /**
+   * Add a listener for changes to the table
+   * @param listener the listener to add
+   * @param flags bitmask specifying desired notifications
+   */
+  public void addTableListenerEx(ITableListener listener, int flags);
+
+  /**
+   * Add a listener for changes to a specific key the table
+   * @param key the key to listen for
+   * @param listener the listener to add
+   * @param immediateNotify if true then this listener will be notified of all
+   * current entries (marked as new)
+   */
+  public void addTableListener(String key, ITableListener listener,
+                               boolean immediateNotify);
+  /**
+   * Add a listener for changes to a specific key the table
+   * @param key the key to listen for
+   * @param listener the listener to add
+   * @param flags bitmask specifying desired notifications
+   */
+  public void addTableListenerEx(String key, ITableListener listener,
+                                 int flags);
+  /**
+   * This will immediately notify the listener of all current sub tables
+   * @param listener the listener to notify
+   */
+  public void addSubTableListener(final ITableListener listener);
+  /**
+   * This will immediately notify the listener of all current sub tables
+   * @param listener the listener to notify
+   * @param localNotify if true then this listener will be notified of all
+   * local changes in addition to all remote changes
+   */
+  public void addSubTableListener(final ITableListener listener,
+                                  boolean localNotify);
+  /**
+   * Remove a listener from receiving table events
+   * @param listener the listener to be removed
+   */
+  public void removeTableListener(ITableListener listener);
+
+  /*
+   * Deprecated Methods
+   */
+
+  /**
+   * Maps the specified key to the specified value in this table.
+   * The key can not be null.
+   * The value can be retrieved by calling the get method with a key that is
+   * equal to the original key.
+   * @param key the key
+   * @param value the value
+   * @return False if the table key already exists with a different type
+   * @throws IllegalArgumentException if key is null
+   * @deprecated Use {@link #putNumber(String, double)} instead.
+   */
+  @Deprecated
+  public boolean putInt(String key, int value);
+
+  /**
+   * Returns the value at the specified key.
+   * @param key the key
+   * @return the value
+   * @throws TableKeyNotDefinedException if there is no value mapped to by the
+   * key
+   * @throws IllegalArgumentException if the value mapped to by the key is not
+   * an int
+   * @throws IllegalArgumentException if the key is null
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Deprecated
+  public int getInt(String key) throws TableKeyNotDefinedException;
+
+  /**
+   * Returns the value at the specified key.
+   * @param key the key
+   * @param defaultValue the value returned if the key is undefined
+   * @return the value
+   * @throws IllegalArgumentException if the value mapped to by the key is not
+   * an int
+   * @throws IllegalArgumentException if the key is null
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Deprecated
+  public int getInt(String key, int defaultValue)
+      throws TableKeyNotDefinedException;
+
+  /**
+   * Maps the specified key to the specified value in this table.
+   * The key can not be null.
+   * The value can be retrieved by calling the get method with a key that is
+   * equal to the original key.
+   * @param key the key
+   * @param value the value
+   * @return False if the table key already exists with a different type
+   * @throws IllegalArgumentException if key is null
+   * @deprecated Use {@link #putNumber(String, double)} instead.
+   */
+  @Deprecated
+  public boolean putDouble(String key, double value);
+
+  /**
+   * Returns the value at the specified key.
+   * @param key the key
+   * @return the value
+   * @throws TableKeyNotDefinedException if there is no
+   * value mapped to by the key
+   * @throws IllegalArgumentException if the value mapped to by the key is not a
+   * double
+   * @throws IllegalArgumentException if the key is null
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Deprecated
+  public double getDouble(String key) throws TableKeyNotDefinedException;
+
+  /**
+   * Returns the value at the specified key.
+   * @param key the key
+   * @param defaultValue the value returned if the key is undefined
+   * @return the value
+   * @throws IllegalArgumentException if the value mapped to by the key is not a
+   * double
+   * @throws IllegalArgumentException if the key is null
+   * @deprecated Use {@link #getNumber(String, double)} instead.
+   */
+  @Deprecated
+  public double getDouble(String key, double defaultValue);
+}
diff --git a/java/src/edu/wpi/first/wpilibj/tables/ITableListener.java b/java/src/edu/wpi/first/wpilibj/tables/ITableListener.java
new file mode 100644
index 0000000..e1d9259
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/tables/ITableListener.java
@@ -0,0 +1,35 @@
+package edu.wpi.first.wpilibj.tables;
+
+/**
+ * A listener that listens to changes in values in a {@link ITable}
+ * 
+ * @author Mitchell
+ *
+ */
+@FunctionalInterface
+public interface ITableListener {
+    /**
+     * Called when a key-value pair is changed in a {@link ITable}
+     * @param source the table the key-value pair exists in
+     * @param key the key associated with the value that changed
+     * @param value the new value
+     * @param isNew true if the key did not previously exist in the table, otherwise it is false
+     */
+    public void valueChanged(ITable source, String key, Object value, boolean isNew);
+
+    /**
+     * Extended version of valueChanged.  Called when a key-value pair is
+     * changed in a {@link ITable}.  The default implementation simply calls
+     * valueChanged().  If this is overridden, valueChanged() will not be
+     * called.
+     * @param source the table the key-value pair exists in
+     * @param key the key associated with the value that changed
+     * @param value the new value
+     * @param flags update flags; for example, NOTIFY_NEW if the key did not
+     * previously exist in the table
+     */
+    default public void valueChangedEx(ITable source, String key, Object value, int flags) {
+        // NOTIFY_NEW = 0x04
+        valueChanged(source, key, value, (flags & 0x04) != 0);
+    }
+}
diff --git a/java/src/edu/wpi/first/wpilibj/tables/TableKeyNotDefinedException.java b/java/src/edu/wpi/first/wpilibj/tables/TableKeyNotDefinedException.java
new file mode 100644
index 0000000..4ca3a54
--- /dev/null
+++ b/java/src/edu/wpi/first/wpilibj/tables/TableKeyNotDefinedException.java
@@ -0,0 +1,20 @@
+package edu.wpi.first.wpilibj.tables;
+
+import java.util.NoSuchElementException;
+
+/**
+ * An exception throw when the lookup a a key-value fails in a {@link ITable}
+ * 
+ * @author Mitchell
+ *
+ */
+public class TableKeyNotDefinedException extends NoSuchElementException {
+
+	/**
+	 * @param key the key that was not defined in the table
+	 */
+	public TableKeyNotDefinedException(String key) {
+		super("Unknown Table Key: "+key);
+	}
+
+}
diff --git a/java/test/Client.java b/java/test/Client.java
new file mode 100644
index 0000000..7f18690
--- /dev/null
+++ b/java/test/Client.java
@@ -0,0 +1,39 @@
+import edu.wpi.first.wpilibj.networktables.*;
+import edu.wpi.first.wpilibj.tables.*;
+
+public class Client {
+  private static class MyLogger implements NetworkTablesJNI.LoggerFunction {
+    public void apply(int level, String file, int line, String msg) {
+      System.err.println(msg);
+    }
+  }
+
+  public static void main(String[] args) {
+    NetworkTablesJNI.setLogger(new MyLogger(), 0);
+    NetworkTable.setIPAddress("127.0.0.1");
+    NetworkTable.setPort(10000);
+    NetworkTable.setClientMode();
+    NetworkTable nt = NetworkTable.getTable("");
+    try { Thread.sleep(2000); } catch (InterruptedException e) {}
+    try {
+      System.out.println("Got foo: " + nt.getNumber("foo"));
+    } catch(TableKeyNotDefinedException ex) {
+    }
+    nt.putBoolean("bar", false);
+    nt.setFlags("bar", NetworkTable.PERSISTENT);
+    nt.putBoolean("bar2", true);
+    nt.putBoolean("bar2", false);
+    nt.putBoolean("bar2", true);
+    nt.putString("str", "hello world");
+    double[] nums = new double[3];
+    nums[0] = 0.5;
+    nums[1] = 1.2;
+    nums[2] = 3.0;
+    nt.putNumberArray("numarray", nums);
+    String[] strs = new String[2];
+    strs[0] = "Hello";
+    strs[1] = "World";
+    nt.putStringArray("strarray", strs);
+    try { Thread.sleep(10000); } catch (InterruptedException e) {}
+  }
+}
diff --git a/java/test/Server.java b/java/test/Server.java
new file mode 100644
index 0000000..33844fb
--- /dev/null
+++ b/java/test/Server.java
@@ -0,0 +1,26 @@
+import edu.wpi.first.wpilibj.networktables.*;
+import edu.wpi.first.wpilibj.tables.*;
+
+public class Server {
+  private static class MyLogger implements NetworkTablesJNI.LoggerFunction {
+    public void apply(int level, String file, int line, String msg) {
+      System.err.println(msg);
+    }
+  }
+
+  public static void main(String[] args) {
+    NetworkTablesJNI.setLogger(new MyLogger(), 0);
+    NetworkTable.setIPAddress("127.0.0.1");
+    NetworkTable.setPort(10000);
+    NetworkTable.setServerMode();
+    NetworkTable nt = NetworkTable.getTable("");
+    try { Thread.sleep(1000); } catch (InterruptedException e) {}
+    nt.putNumber("foo", 0.5);
+    nt.setFlags("foo", NetworkTable.PERSISTENT);
+    nt.putNumber("foo2", 0.5);
+    nt.putNumber("foo2", 0.7);
+    nt.putNumber("foo2", 0.6);
+    nt.putNumber("foo2", 0.5);
+    try { Thread.sleep(10000); } catch (InterruptedException e) {}
+  }
+}