diff --git a/src/external/CMakeLists.txt b/src/external/CMakeLists.txt index ed0fff42e..04595cfdc 100644 --- a/src/external/CMakeLists.txt +++ b/src/external/CMakeLists.txt @@ -25,6 +25,13 @@ target_include_directories(xrt-external-glad INTERFACE ${CMAKE_CURRENT_SOURCE_DI add_library(xrt-external-hungarian INTERFACE) target_include_directories(xrt-external-hungarian INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/hungarian) +# JNIPP +if(ANDROID) + add_library(xrt-external-jnipp STATIC + jnipp/jnipp.cpp) + target_include_directories(xrt-external-jnipp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/jnipp) +endif() + # OpenXR add_library(xrt-external-openxr INTERFACE) target_include_directories(xrt-external-openxr INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/openxr_includes) diff --git a/src/external/jnipp/.gitignore b/src/external/jnipp/.gitignore new file mode 100644 index 000000000..f6233e2c5 --- /dev/null +++ b/src/external/jnipp/.gitignore @@ -0,0 +1,258 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml +*.out +*.o +/test + +# Gradle +.gradle/ diff --git a/src/external/jnipp/Android.bp b/src/external/jnipp/Android.bp new file mode 100644 index 000000000..efb9575ad --- /dev/null +++ b/src/external/jnipp/Android.bp @@ -0,0 +1,23 @@ + +cc_library_headers { + name: "libjnipp-headers", + export_include_dirs: ["."], + vendor_available: true, +} + +cc_library_static { + name: "libjnipp", + + srcs: [ + "jnipp.cpp", + ], + + cppflags: [ "-fexceptions" ], + header_libs: [ + "libjnipp-headers", + ], + export_header_lib_headers: [ + "libjnipp-headers" + ], + vendor_available: true, +} diff --git a/src/external/jnipp/LICENSE b/src/external/jnipp/LICENSE new file mode 100644 index 000000000..bda04ad85 --- /dev/null +++ b/src/external/jnipp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Mitchell Dowd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/external/jnipp/README.md b/src/external/jnipp/README.md new file mode 100644 index 000000000..178708bee --- /dev/null +++ b/src/external/jnipp/README.md @@ -0,0 +1,107 @@ +# Java Native Interface for C++ + +## Overview + +JNIPP is just a C++ wrapper for the standard Java Native Interface (JNI). It +tries to take some of the long-winded annoyance out of integrating your Java +and C++ code. + +While this project has so far just been a utility library for my own usage, +it seems to have caught the eye of some others who have also been looking for +a suitable C++ JNI layer. If you have feature requests, do not hesitate to +submit them as Github issues. Please be descriptive in your feature request. +The more useful information you provide - along with justification on why it +should be implemented - the more likely it is that I will add your feature. + +## Requirements + +To compile you will need: + +- A C++11 compatible compiler +- An installation of the Java Development Kit (JDK) +- The `JAVA_HOME` environment variable, directed to your JDK installation. + +## Usage + +> For comprehensive examples on how to use *jnipp*, see the `tests` project +> in the project source code. + +There are two situations where the Java Native Interface would be needed. + +- A Java application calling C/C++ functions; or +- A C/C++ application calling Java methods + +### Calling Java from C++ + +The following is an example of calling Java from C++. + +```C++ +#include + +int main() +{ + // An instance of the Java VM needs to be created. + jni::Vm vm; + + // Create an instance of java.lang.Integer + jni::Class Integer = jni::Class("java/lang/Integer"); + jni::Object i = Integer.newInstance("1000"); + + // Call the `toString` method on that integer + std::string str = i.call("toString"); + + // The Java VM is automatically destroyed when it goes out of scope. + return 0; +} +``` + +### Calling C++ from Java + +Consider a basic Java program: + +```Java +package com.example; + +class Demo { + public int value; + + public static void main(String[] args) { + Demo demo = new Demo(); + demo.value = 1000; + demo.run(); + } + + public native void run(); +} +``` + +A matching C++ library which uses *jnipp* could look like: + +```C++ +#include +#include + +/* + The signature here is defind by the JNI standard, so must be adhered to. + Although, to prevent pollution of the global namespace, the JNIEnv and + jobject types defind by the standard JNI have been placed into the + jni namespace. + */ +extern "C" void Java_com_example_Demo_run(jni::JNIEnv* env, jni::jobject obj) +{ + // jnipp only needs initialising once, but it doesn't hurt to do it again. + jni::init(env); + + // Capture the supplied object. + jni::Object demo(obj); + + // Print the contents of the `value` field to stdout. + std::cout << demo.get("value") << std::endl; +} +``` + +## Configuration + +By default, *jnipp* uses std::runtime_error as the base exception class. If you wish, +you can define `JNIPP_EXCEPTION_CLASS` to be the exception class you wish to use, before +including `jnipp.h`. It just needs a `const char*` constructor. diff --git a/src/external/jnipp/android/AndroidManifest.xml b/src/external/jnipp/android/AndroidManifest.xml new file mode 100644 index 000000000..7ea988b9c --- /dev/null +++ b/src/external/jnipp/android/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/src/external/jnipp/android/CMakeLists.txt b/src/external/jnipp/android/CMakeLists.txt new file mode 100644 index 000000000..cca518d47 --- /dev/null +++ b/src/external/jnipp/android/CMakeLists.txt @@ -0,0 +1,12 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.4.1) + +GET_FILENAME_COMPONENT(root_dir "../.." ABSOLUTE) + +LINK_DIRECTORIES("${root_dir}/${ANDROID_ABI}") + +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${root_dir}/${ANDROID_ABI}") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID_STL=c++_static -std=gnu++11 -fexceptions") + +FILE(GLOB sources ../*.cpp ../common/*.cpp ../android/*.cpp) + +ADD_LIBRARY(jnipp SHARED ${sources}) diff --git a/src/external/jnipp/build.gradle b/src/external/jnipp/build.gradle new file mode 100644 index 000000000..a17e95a70 --- /dev/null +++ b/src/external/jnipp/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 26 + buildToolsVersion '28.0.3' + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 26 + } + + buildTypes { + release { + minifyEnabled = false + proguardFiles getDefaultProguardFile('proguard-android.txt') + } + } + + sourceSets { + main { + manifest.srcFile 'android/AndroidManifest.xml' + } + } + + externalNativeBuild { + cmake { + path 'android/CMakeLists.txt' + } + } +} diff --git a/src/external/jnipp/dotest.sh b/src/external/jnipp/dotest.sh new file mode 100755 index 000000000..505663be4 --- /dev/null +++ b/src/external/jnipp/dotest.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e +( + cd $(dirname $0) + export JAVA_HOME=/home/ryan/apps/android-studio/jre/ + make "$@" + if [ -x ./test ]; then + ./test + fi +) diff --git a/src/external/jnipp/jnipp.cpp b/src/external/jnipp/jnipp.cpp new file mode 100644 index 000000000..cc9941be1 --- /dev/null +++ b/src/external/jnipp/jnipp.cpp @@ -0,0 +1,1531 @@ +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 + + // Windows Dependencies +# include +#else + // UNIX Dependencies +# include +#endif + +// External Dependencies +#include + +// Standard Dependencies +#include +#include + +// Local Dependencies +#include "jnipp.h" + +namespace jni +{ + // Static Variables + static std::atomic_bool isVm(false); + static JavaVM* javaVm = nullptr; + + /** + Maintains the lifecycle of a JNIEnv. + */ + class ScopedEnv final + { + public: + ScopedEnv() noexcept : _vm(nullptr), _env(nullptr), _attached(false) {} + ~ScopedEnv(); + + void init(JavaVM* vm); + JNIEnv* get() const noexcept { return _env; } + + private: + // Instance Variables + JavaVM* _vm; + JNIEnv* _env; + bool _attached; ///< Manually attached, as opposed to already attached. + }; + + ScopedEnv::~ScopedEnv() + { + if (_vm && _attached) + _vm->DetachCurrentThread(); + } + + void ScopedEnv::init(JavaVM* vm) + { + if (_env != nullptr) + return; + + if (vm == nullptr) + throw InitializationException("JNI not initialized"); + + if (vm->GetEnv((void**)&_env, JNI_VERSION_1_2) != JNI_OK) + { +#ifdef __ANDROID__ + if (vm->AttachCurrentThread(&_env, nullptr) != 0) +#else + if (vm->AttachCurrentThread((void**)&_env, nullptr) != 0) +#endif + throw InitializationException("Could not attach JNI to thread"); + + _attached = true; + } + + _vm = vm; + } + + /* + Helper Functions + */ + +#ifdef _WIN32 + + static bool fileExists(const std::string& filePath) + { + DWORD attr = ::GetFileAttributesA(filePath.c_str()); + + return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY); + } + +#else + + /** + Convert from a UTF-16 Java string to a UTF-32 string. + */ + std::wstring toWString(const jchar* str, jsize length) + { + std::wstring result; + + result.reserve(length); + + for (jsize i = 0; i < length; ++i) + { + wchar_t ch = str[i]; + + // Check for a two-segment character. + if (ch >= wchar_t(0xD800) && ch <= wchar_t(0xDBFF)) { + if (i + 1 >= length) + break; + + // Create a single, 32-bit character. + ch = (ch - wchar_t(0xD800)) << 10; + ch += str[i++] - wchar_t(0x1DC00); + } + + result += ch; + } + + return result; + } + + /** + Convert from a UTF-32 string to a UTF-16 Java string. + */ + std::basic_string toJString(const wchar_t* str, size_t length) + { + std::basic_string result; + + result.reserve(length * 2); // Worst case scenario. + + for (size_t i = 0; i < length; ++i) + { + wchar_t ch = str[i]; + + // Check for multi-byte UTF-16 character. + if (ch > wchar_t(0xFFFF)) { + ch -= uint32_t(0x10000); + + // Add the first of the two-segment character. + result += jchar(0xD800 + (ch >> 10)); + ch = wchar_t(0xDC00) + (ch & 0x03FF); + } + + result += jchar(ch); + } + + return result; + } + +#endif // _WIN32 + + JNIEnv* env() + { + static thread_local ScopedEnv env; + + if (env.get() == nullptr) + env.init(javaVm); + + return env.get(); + } + + static jclass findClass(const char* name) + { + jclass ref = env()->FindClass(name); + + if (ref == nullptr) + { + env()->ExceptionClear(); + throw NameResolutionException(name); + } + + return ref; + } + + static void handleJavaExceptions() + { + JNIEnv* env = jni::env(); + + jthrowable exception = env->ExceptionOccurred(); + + if (exception != nullptr) + { + Object obj(exception, Object::Temporary); + + env->ExceptionClear(); + std::string msg = obj.call("toString"); + throw InvocationException(msg.c_str()); + } + } + + static std::string toString(jobject handle, bool deleteLocal = true) + { + std::string result; + + if (handle != nullptr) + { + JNIEnv* env = jni::env(); + + const char* chars = env->GetStringUTFChars(jstring(handle), nullptr); + result.assign(chars, env->GetStringUTFLength(jstring(handle))); + env->ReleaseStringUTFChars(jstring(handle), chars); + + if (deleteLocal) + env->DeleteLocalRef(handle); + } + + return result; + } + + static std::wstring toWString(jobject handle, bool deleteLocal = true) + { + std::wstring result; + + if (handle != nullptr) + { + JNIEnv* env = jni::env(); + + const jchar* chars = env->GetStringChars(jstring(handle), nullptr); +#ifdef _WIN32 + result.assign((const wchar_t*) chars, env->GetStringLength(jstring(handle))); +#else + result = toWString(chars, env->GetStringLength(jstring(handle))); +#endif + env->ReleaseStringChars(jstring(handle), chars); + + if (deleteLocal) + env->DeleteLocalRef(handle); + } + + return result; + } + + + /* + Stand-alone Function Implementations + */ + + void init(JNIEnv* env) + { + bool expected = false; + + if (isVm.compare_exchange_strong(expected, true)) + { + if (javaVm == nullptr && env->GetJavaVM(&javaVm) != 0) + throw InitializationException("Could not acquire Java VM"); + } + } + + void init(JavaVM* vm) { + bool expected = false; + + if (isVm.compare_exchange_strong(expected, true)) + { + javaVm = vm; + } + } + /* + Object Implementation + */ + + Object::Object() noexcept : _handle(nullptr), _class(nullptr), _isGlobal(false) + { + } + + Object::Object(const Object& other) : _handle(nullptr), _class(nullptr), _isGlobal(!other.isNull()) + { + if (!other.isNull()) + _handle = env()->NewGlobalRef(other._handle); + } + + Object::Object(Object&& other) noexcept : _handle(other._handle), _class(other._class), _isGlobal(other._isGlobal) + { + other._handle = nullptr; + other._class = nullptr; + other._isGlobal = false; + } + + Object::Object(jobject ref, int scopeFlags) : _handle(ref), _class(nullptr), _isGlobal((scopeFlags & Temporary) == 0) + { + if (!_isGlobal) + return; + + JNIEnv* env = jni::env(); + + _handle = env->NewGlobalRef(ref); + + if (scopeFlags & DeleteLocalInput) + env->DeleteLocalRef(ref); + } + + Object::~Object() noexcept + { + JNIEnv* env = jni::env(); + + if (_isGlobal) + env->DeleteGlobalRef(_handle); + + if (_class != nullptr) + env->DeleteGlobalRef(_class); + } + + Object& Object::operator=(const Object& other) + { + if (_handle != other._handle) + { + JNIEnv* env = jni::env(); + + // Ditch the old reference. + if (_isGlobal) + env->DeleteGlobalRef(_handle); + if (_class != nullptr) + env->DeleteGlobalRef(_class); + + // Assign the new reference. + if ((_isGlobal = !other.isNull()) != false) + _handle = env->NewGlobalRef(other._handle); + + _class = nullptr; + } + + return *this; + } + + bool Object::operator==(const Object& other) const + { + return env()->IsSameObject(_handle, other._handle) != JNI_FALSE; + } + + Object& Object::operator=(Object&& other) + { + if (_handle != other._handle) + { + JNIEnv* env = jni::env(); + + // Ditch the old reference. + if (_isGlobal) + env->DeleteGlobalRef(_handle); + if (_class != nullptr) + env->DeleteGlobalRef(_class); + + // Assign the new reference. + _handle = other._handle; + _isGlobal = other._isGlobal; + _class = other._class; + + other._handle = nullptr; + other._isGlobal = false; + other._class = nullptr; + } + + return *this; + } + + bool Object::isNull() const noexcept + { + return _handle == nullptr || env()->IsSameObject(_handle, nullptr); + } + + template <> void Object::callMethod(method_t method, internal::value_t* args) const + { + env()->CallVoidMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + } + + template <> bool Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallBooleanMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result != 0; + } + + template <> bool Object::get(field_t field) const + { + return env()->GetBooleanField(_handle, field) != 0; + } + + template <> void Object::set(field_t field, const bool& value) + { + env()->SetBooleanField(_handle, field, value); + } + + template <> byte_t Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallByteMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> wchar_t Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallCharMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> short Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallShortMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> int Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallIntMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> long long Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallLongMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> float Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallFloatMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> double Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallDoubleMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> std::string Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return toString(result); + } + + template <> std::wstring Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return toWString(result); + } + + template <> jni::Object Object::callMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallObjectMethodA(_handle, method, (jvalue*) args); + handleJavaExceptions(); + return Object(result, DeleteLocalInput); + } + + template <> byte_t Object::get(field_t field) const + { + return env()->GetByteField(_handle, field); + } + + template <> wchar_t Object::get(field_t field) const + { + return env()->GetCharField(_handle, field); + } + + template <> short Object::get(field_t field) const + { + return env()->GetShortField(_handle, field); + } + + template <> int Object::get(field_t field) const + { + return env()->GetIntField(_handle, field); + } + + template <> long long Object::get(field_t field) const + { + return env()->GetLongField(_handle, field); + } + + template <> float Object::get(field_t field) const + { + return env()->GetFloatField(_handle, field); + } + + template <> double Object::get(field_t field) const + { + return env()->GetDoubleField(_handle, field); + } + + template <> std::string Object::get(field_t field) const + { + return toString(env()->GetObjectField(_handle, field)); + } + + template <> std::wstring Object::get(field_t field) const + { + return toWString(env()->GetObjectField(_handle, field)); + } + + template <> Object Object::get(field_t field) const + { + return Object(env()->GetObjectField(_handle, field), DeleteLocalInput); + } + + template <> void Object::set(field_t field, const byte_t& value) + { + env()->SetByteField(_handle, field, value); + } + + template <> void Object::set(field_t field, const wchar_t& value) + { + env()->SetCharField(_handle, field, value); + } + + template <> void Object::set(field_t field, const short& value) + { + env()->SetShortField(_handle, field, value); + } + + template <> void Object::set(field_t field, const int& value) + { + env()->SetIntField(_handle, field, value); + } + + template <> void Object::set(field_t field, const long long& value) + { + env()->SetLongField(_handle, field, value); + } + + template <> void Object::set(field_t field, const float& value) + { + env()->SetFloatField(_handle, field, value); + } + + template <> void Object::set(field_t field, const double& value) + { + env()->SetDoubleField(_handle, field, value); + } + + template <> void Object::set(field_t field, const std::string& value) + { + JNIEnv* env = jni::env(); + + jobject handle = env->NewStringUTF(value.c_str()); + env->SetObjectField(_handle, field, handle); + env->DeleteLocalRef(handle); + } + + template <> void Object::set(field_t field, const std::wstring& value) + { + JNIEnv* env = jni::env(); + +#ifdef _WIN32 + jobject handle = env->NewString((const jchar*) value.c_str(), jsize(value.length())); +#else + auto jstr = toJString(value.c_str(), value.length()); + jobject handle = env->NewString(jstr.c_str(), jsize(jstr.length())); +#endif + env->SetObjectField(_handle, field, handle); + env->DeleteLocalRef(handle); + } + + template <> void Object::set(field_t field, const wchar_t* const& value) + { + JNIEnv* env = jni::env(); +#ifdef _WIN32 + jobject handle = env->NewString((const jchar*) value, jsize(std::wcslen(value))); +#else + auto jstr = toJString(value, std::wcslen(value)); + jobject handle = env->NewString(jstr.c_str(), jsize(jstr.length())); +#endif + env->SetObjectField(_handle, field, handle); + env->DeleteLocalRef(handle); + } + + template <> void Object::set(field_t field, const char* const& value) + { + JNIEnv* env = jni::env(); + + jobject handle = env->NewStringUTF(value); + env->SetObjectField(_handle, field, handle); + env->DeleteLocalRef(handle); + } + + template <> void Object::set(field_t field, const Object& value) + { + env()->SetObjectField(_handle, field, value.getHandle()); + } + + template <> void Object::set(field_t field, const Object* const& value) + { + env()->SetObjectField(_handle, field, value ? value->getHandle() : nullptr); + } + + jclass Object::getClass() const + { + if (_class == nullptr) + { + JNIEnv* env = jni::env(); + + jclass classRef = env->GetObjectClass(_handle); + _class = jclass(env->NewGlobalRef(classRef)); + env->DeleteLocalRef(classRef); + } + + return _class; + } + + method_t Object::getMethod(const char* name, const char* signature) const + { + return Class(getClass(), Temporary).getMethod(name, signature); + } + + method_t Object::getMethod(const char* nameAndSignature) const + { + return Class(getClass(), Temporary).getMethod(nameAndSignature); + } + + field_t Object::getField(const char* name, const char* signature) const + { + return Class(getClass(), Temporary).getField(name, signature); + } + + jobject Object::makeLocalReference() const + { + if (isNull()) + return nullptr; + return env()->NewLocalRef(_handle); + } + + /* + Class Implementation + */ + + Class::Class(const char* name) : Object(findClass(name), DeleteLocalInput) + { + } + + Class::Class(jclass ref, int scopeFlags) : Object(ref, scopeFlags) + { + } + + Object Class::newInstance() const + { + method_t constructor = getMethod("", "()V"); + jobject obj = env()->NewObject(getHandle(), constructor); + + handleJavaExceptions(); + + return Object(obj, Object::DeleteLocalInput); + } + + field_t Class::getField(const char* name, const char* signature) const + { + jfieldID id = env()->GetFieldID(getHandle(), name, signature); + + if (id == nullptr) + throw NameResolutionException(name); + + return id; + } + + field_t Class::getStaticField(const char* name, const char* signature) const + { + jfieldID id = env()->GetStaticFieldID(getHandle(), name, signature); + + if (id == nullptr) + throw NameResolutionException(name); + + return id; + } + + method_t Class::getMethod(const char* name, const char* signature) const + { + jmethodID id = env()->GetMethodID(getHandle(), name, signature); + + if (id == nullptr) + throw NameResolutionException(name); + + return id; + } + + + method_t Class::getMethod(const char* nameAndSignature) const + { + jmethodID id = nullptr; + const char* sig = std::strchr(nameAndSignature, '('); + + if (sig != nullptr) + return getMethod(std::string(nameAndSignature, sig - nameAndSignature).c_str(), sig); + + if (id == nullptr) + throw NameResolutionException(nameAndSignature); + + return id; + } + + method_t Class::getStaticMethod(const char* name, const char* signature) const + { + jmethodID id = env()->GetStaticMethodID(getHandle(), name, signature); + + if (id == nullptr) + throw NameResolutionException(name); + + return id; + } + + method_t Class::getStaticMethod(const char* nameAndSignature) const + { + jmethodID id = nullptr; + const char* sig = std::strchr(nameAndSignature, '('); + + if (sig != nullptr) + return getStaticMethod(std::string(nameAndSignature, sig - nameAndSignature).c_str(), sig); + + if (id == nullptr) + throw NameResolutionException(nameAndSignature); + + return id; + } + + Class Class::getParent() const + { + return Class(env()->GetSuperclass(getHandle()), DeleteLocalInput); + } + + std::string Class::getName() const + { + return Object::call("getName"); + } + + template <> bool Class::get(field_t field) const + { + return env()->GetStaticBooleanField(getHandle(), field) != 0; + } + + template <> byte_t Class::get(field_t field) const + { + return env()->GetStaticByteField(getHandle(), field); + } + + template <> wchar_t Class::get(field_t field) const + { + return env()->GetStaticCharField(getHandle(), field); + } + + template <> short Class::get(field_t field) const + { + return env()->GetStaticShortField(getHandle(), field); + } + + template <> int Class::get(field_t field) const + { + return env()->GetStaticIntField(getHandle(), field); + } + + template <> long long Class::get(field_t field) const + { + return env()->GetStaticLongField(getHandle(), field); + } + + template <> float Class::get(field_t field) const + { + return env()->GetStaticFloatField(getHandle(), field); + } + + template <> double Class::get(field_t field) const + { + return env()->GetStaticDoubleField(getHandle(), field); + } + + template <> std::string Class::get(field_t field) const + { + return toString(env()->GetStaticObjectField(getHandle(), field)); + } + + template <> std::wstring Class::get(field_t field) const + { + return toWString(env()->GetStaticObjectField(getHandle(), field)); + } + + template <> Object Class::get(field_t field) const + { + return Object(env()->GetStaticObjectField(getHandle(), field), DeleteLocalInput); + } + + template <> void Class::set(field_t field, const bool& value) + { + env()->SetStaticBooleanField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const byte_t& value) + { + env()->SetStaticByteField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const wchar_t& value) + { + env()->SetStaticCharField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const short& value) + { + env()->SetStaticShortField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const int& value) + { + env()->SetStaticIntField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const long long& value) + { + env()->SetStaticLongField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const float& value) + { + env()->SetStaticFloatField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const double& value) + { + env()->SetStaticDoubleField(getHandle(), field, value); + } + + template <> void Class::set(field_t field, const Object& value) + { + env()->SetStaticObjectField(getHandle(), field, value.getHandle()); + } + + template <> void Class::set(field_t field, const Object* const& value) + { + env()->SetStaticObjectField(getHandle(), field, value ? value->getHandle() : nullptr); + } + + template <> void Class::set(field_t field, const std::string& value) + { + JNIEnv* env = jni::env(); + + jobject handle = env->NewStringUTF(value.c_str()); + env->SetStaticObjectField(getHandle(), field, handle); + env->DeleteLocalRef(handle); + } + + template <> void Class::set(field_t field, const std::wstring& value) + { + JNIEnv* env = jni::env(); + +#ifdef _WIN32 + jobject handle = env->NewString((const jchar*) value.c_str(), jsize(value.length())); +#else + auto jstr = toJString(value.c_str(), value.length()); + jobject handle = env->NewString(jstr.c_str(), jsize(jstr.length())); +#endif + env->SetStaticObjectField(getHandle(), field, handle); + env->DeleteLocalRef(handle); + } + + template <> void Class::callStaticMethod(method_t method, internal::value_t* args) const + { + env()->CallStaticVoidMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + } + + template <> bool Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticBooleanMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result != 0; + } + + template <> byte_t Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticByteMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> wchar_t Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticCharMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> short Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticShortMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> int Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticIntMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> long long Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticLongMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> float Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticFloatMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> double Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticDoubleMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> std::string Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticObjectMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return toString(result); + } + + template <> std::wstring Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticObjectMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return toWString(result); + } + + template <> jni::Object Class::callStaticMethod(method_t method, internal::value_t* args) const + { + auto result = env()->CallStaticObjectMethodA(getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return Object(result, DeleteLocalInput); + } + + template <> void Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + env()->CallNonvirtualVoidMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + } + + template <> bool Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualBooleanMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result != 0; + } + + template <> byte_t Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualByteMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> wchar_t Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualCharMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> short Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualShortMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> int Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualIntMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> long long Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualLongMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> float Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualFloatMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> double Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualDoubleMethodA(obj, getHandle(), method, (jvalue*) args); + handleJavaExceptions(); + return result; + } + + template <> std::string Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualObjectMethodA(obj, getHandle(), method, (jvalue*)args); + handleJavaExceptions(); + return toString(result); + } + + template <> std::wstring Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualObjectMethodA(obj, getHandle(), method, (jvalue*)args); + handleJavaExceptions(); + return toWString(result); + } + + template <> Object Class::callExactMethod(jobject obj, method_t method, internal::value_t* args) const + { + auto result = env()->CallNonvirtualObjectMethodA(obj, getHandle(), method, (jvalue*)args); + handleJavaExceptions(); + return Object(result, DeleteLocalInput); + } + + Object Class::newObject(method_t constructor, internal::value_t* args) const + { + jobject ref = env()->NewObjectA(getHandle(), constructor, (jvalue*)args); + handleJavaExceptions(); + return Object(ref, DeleteLocalInput); + } + + /* + Enum Implementation + */ + + Enum::Enum(const char* name) : Class(name) + { + _name = "L"; + _name += name; + _name += ";"; + } + + Object Enum::get(const char* name) const + { + return Class::get(getStaticField(name, _name.c_str())); + } + + /* + Array Implementation + */ + + template <> Array::Array(long length) : Object(env()->NewBooleanArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewByteArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewCharArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewShortArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewIntArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewLongArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewFloatArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewDoubleArray(length)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewObjectArray(length, Class("java/lang/String").getHandle(), nullptr)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewObjectArray(length, Class("java/lang/String").getHandle(), nullptr)), _length(length) + { + } + + template <> Array::Array(long length) : Object(env()->NewObjectArray(length, Class("java/lang/Object").getHandle(), nullptr)), _length(length) + { + } + + template <> Array::Array(long length, const Class& type) : Object(env()->NewObjectArray(length, type.getHandle(), nullptr)), _length(length) + { + } + + template <> bool Array::getElement(long index) const + { + jboolean output; + env()->GetBooleanArrayRegion(jbooleanArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> byte_t Array::getElement(long index) const + { + jbyte output; + env()->GetByteArrayRegion(jbyteArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> wchar_t Array::getElement(long index) const + { + jchar output; + env()->GetCharArrayRegion(jcharArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> short Array::getElement(long index) const + { + jshort output; + env()->GetShortArrayRegion(jshortArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> int Array::getElement(long index) const + { + jint output; + env()->GetIntArrayRegion(jintArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> long long Array::getElement(long index) const + { + jlong output; + env()->GetLongArrayRegion(jlongArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> float Array::getElement(long index) const + { + jfloat output; + env()->GetFloatArrayRegion(jfloatArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> double Array::getElement(long index) const + { + jdouble output; + env()->GetDoubleArrayRegion(jdoubleArray(getHandle()), index, 1, &output); + handleJavaExceptions(); + return output; + } + + template <> std::string Array::getElement(long index) const + { + jobject output = env()->GetObjectArrayElement(jobjectArray(getHandle()), index); + handleJavaExceptions(); + return toString(output); + } + + template <> std::wstring Array::getElement(long index) const + { + jobject output = env()->GetObjectArrayElement(jobjectArray(getHandle()), index); + handleJavaExceptions(); + return toWString(output); + } + + template <> Object Array::getElement(long index) const + { + jobject output = env()->GetObjectArrayElement(jobjectArray(getHandle()), index); + handleJavaExceptions(); + return Object(output, DeleteLocalInput); + } + + template <> void Array::setElement(long index, bool value) + { + jboolean jvalue = value; + env()->SetBooleanArrayRegion(jbooleanArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, byte_t value) + { + jbyte jvalue = value; + env()->SetByteArrayRegion(jbyteArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, wchar_t value) + { + jchar jvalue = value; + env()->SetCharArrayRegion(jcharArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, short value) + { + jshort jvalue = value; + env()->SetShortArrayRegion(jshortArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, int value) + { + jint jvalue = value; + env()->SetIntArrayRegion(jintArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, long long value) + { + jlong jvalue = value; + env()->SetLongArrayRegion(jlongArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, float value) + { + jfloat jvalue = value; + env()->SetFloatArrayRegion(jfloatArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, double value) + { + jdouble jvalue = value; + env()->SetDoubleArrayRegion(jdoubleArray(getHandle()), index, 1, &jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, std::string value) + { + JNIEnv* env = jni::env(); + + jobject jvalue = env->NewStringUTF(value.c_str());; + env->SetObjectArrayElement(jobjectArray(getHandle()), index, jvalue); + env->DeleteLocalRef(jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, std::wstring value) + { + JNIEnv* env = jni::env(); + +#ifdef _WIN32 + jobject jvalue = env->NewString((const jchar*) value.c_str(), jsize(value.length())); +#else + auto jstr = toJString(value.c_str(), value.length()); + jobject jvalue = env->NewString(jstr.c_str(), jsize(jstr.length())); +#endif + env->SetObjectArrayElement(jobjectArray(getHandle()), index, jvalue); + env->DeleteLocalRef(jvalue); + handleJavaExceptions(); + } + + template <> void Array::setElement(long index, Object value) + { + env()->SetObjectArrayElement(jobjectArray(getHandle()), index, value.getHandle()); + handleJavaExceptions(); + } + + /* + Vm Implementation + */ + + typedef jint (JNICALL *CreateVm_t)(JavaVM**, void**, void*); + + + static std::string detectJvmPath() + { + std::string result; + +#ifdef _WIN32 + + BYTE buffer[1024]; + DWORD size = sizeof(buffer); + HKEY versionKey; + + // Search via registry entries. + if (::RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\JavaSoft\\Java Runtime Environment\\", &versionKey) == ERROR_SUCCESS) + { + if (::RegQueryValueEx(versionKey, "CurrentVersion", NULL, NULL, buffer, &size) == ERROR_SUCCESS) + { + HKEY libKey; + + std::string keyName = std::string("Software\\JavaSoft\\Java Runtime Environment\\") + (const char*)buffer; + + ::RegCloseKey(versionKey); + + if (::RegOpenKeyA(HKEY_LOCAL_MACHINE, keyName.c_str(), &libKey) == ERROR_SUCCESS) + { + size = sizeof(buffer); + + if (::RegQueryValueEx(libKey, "RuntimeLib", NULL, NULL, buffer, &size) == ERROR_SUCCESS) + { + result = (const char*)buffer; + } + + ::RegCloseKey(libKey); + } + } + } + + if (result.length() == 0) + { + // Could not locate via registry. Try the JAVA_HOME environment variable. + if ((size = ::GetEnvironmentVariableA("JAVA_HOME", (LPSTR) buffer, sizeof(buffer))) != 0) + { + std::string javaHome((const char*) buffer, size); + + // Different installers put in different relative locations. + std::string options[] = { + javaHome + "\\jre\\bin\\client\\jvm.dll", + javaHome + "\\jre\\bin\\server\\jvm.dll", + javaHome + "\\bin\\client\\jvm.dll", + javaHome + "\\bin\\server\\jvm.dll" + }; + + for (auto i : options) + if (fileExists(i)) + return i; + } + } + +#else + + const char* javaHome = getenv("JAVA_HOME"); + if (javaHome != nullptr) { + #ifdef __APPLE__ + std::string libJvmPath = std::string(javaHome) + "/jre/lib/server/libjvm.dylib"; + #else + std::string libJvmPath = std::string(javaHome) + "/jre/lib/amd64/server/libjvm.so"; + #endif + result = libJvmPath; + } else { + // Best guess so far. + result = "/usr/lib/jvm/default-java/jre/lib/amd64/server/libjvm.so"; + } + +#endif // _WIN32 + + return result; + } + + Vm::Vm(const char* path_) + { + bool expected = false; + + std::string path = path_ ? path_ : detectJvmPath(); + + if (path.length() == 0) + throw InitializationException("Could not locate Java Virtual Machine"); + if (!isVm.compare_exchange_strong(expected, true)) + throw InitializationException("Java Virtual Machine already initialized"); + + if (javaVm == nullptr) + { + JNIEnv* env; + JavaVMInitArgs args = {}; + args.version = JNI_VERSION_1_2; + +#ifdef _WIN32 + + HMODULE lib = ::LoadLibraryA(path.c_str()); + + if (lib == NULL) + { + isVm.store(false); + throw InitializationException("Could not load JVM library"); + } + + CreateVm_t JNI_CreateJavaVM = (CreateVm_t) ::GetProcAddress(lib, "JNI_CreateJavaVM"); + + /** + Is your debugger catching an error here? This is normal. Just continue. The JVM + intentionally does this to test how the OS handles memory-reference exceptions. + */ + if (JNI_CreateJavaVM == NULL || JNI_CreateJavaVM(&javaVm, (void**) &env, &args) != 0) + { + isVm.store(false); + ::FreeLibrary(lib); + throw InitializationException("Java Virtual Machine failed during creation"); + } + +#else + + void* lib = ::dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL); + + if (lib == NULL) + { + isVm.store(false); + throw InitializationException("Could not load JVM library"); + } + + CreateVm_t JNI_CreateJavaVM = (CreateVm_t) ::dlsym(lib, "JNI_CreateJavaVM"); + + if (JNI_CreateJavaVM == NULL || JNI_CreateJavaVM(&javaVm, (void**) &env, &args) != 0) + { + isVm.store(false); + ::dlclose(lib); + throw InitializationException("Java Virtual Machine failed during creation"); + } + +#endif // _WIN32 + } + } + + Vm::~Vm() + { + /* + Note that you can't ever *really* unload the JavaVM. If you call + DestroyJavaVM(), you can't then call JNI_CreateJavaVM() again. + So, instead we just flag it as "gone". + */ + isVm.store(false); + } + + // Forward Declarations + JNIEnv* env(); + +#ifndef _WIN32 + extern std::basic_string toJString(const wchar_t* str, size_t length); +#endif + + namespace internal + { + // Base Type Conversions + void valueArg(value_t* v, bool a) { ((jvalue*) v)->z = jboolean(a); } + void valueArg(value_t* v, byte_t a) { ((jvalue*) v)->b = a; } + void valueArg(value_t* v, wchar_t a) { ((jvalue*) v)->c = jchar(a); } // Note: Possible truncation. + void valueArg(value_t* v, short a) { ((jvalue*) v)->s = a; } + void valueArg(value_t* v, int a) { ((jvalue*) v)->i = a; } + void valueArg(value_t* v, long long a) { ((jvalue*) v)->j = a; } + void valueArg(value_t* v, float a) { ((jvalue*) v)->f = a; } + void valueArg(value_t* v, double a) { ((jvalue*) v)->d = a; } + void valueArg(value_t* v, jobject a) { ((jvalue*) v)->l = a; } + void valueArg(value_t* v, const Object& a) { ((jvalue*) v)->l = a.getHandle(); } + void valueArg(value_t* v, const Object* const& a) { ((jvalue*) v)->l = a ? a->getHandle() : nullptr; } + + /* + Object Implementations + */ + + std::string valueSig(const Object* obj) + { + if (obj == nullptr || obj->isNull()) + return "Ljava/lang/Object;"; // One can always hope... + + std::string name = Class(obj->getClass(), Object::Temporary).getName(); + + // Change from "java.lang.Object" format to "java/lang/Object"; + for (size_t i = 0; i < name.length(); ++i) + if (name[i] == '.') + name[i] = '/'; + + return "L" + name + ";"; + } + + /* + String Implementations + */ + + void valueArg(value_t* v, const std::string& a) + { + ((jvalue*) v)->l = env()->NewStringUTF(a.c_str()); + } + + template <> void cleanupArg(value_t* v) + { + env()->DeleteLocalRef(((jvalue*) v)->l); + } + + void valueArg(value_t* v, const char* a) + { + ((jvalue*) v)->l = env()->NewStringUTF(a); + } + + + template <> void cleanupArg(value_t* v) + { + env()->DeleteLocalRef(((jvalue*) v)->l); + } +#ifdef _WIN32 + + void valueArg(value_t* v, const std::wstring& a) + { + ((jvalue*) v)->l = env()->NewString((const jchar*) a.c_str(), jsize(a.length())); + } + + void valueArg(value_t* v, const wchar_t* a) + { + ((jvalue*) v)->l = env()->NewString((const jchar*) a, jsize(std::wcslen(a))); + } +#else + + void valueArg(value_t* v, const std::wstring& a) + { + auto jstr = toJString(a.c_str(), a.length()); + ((jvalue*) v)->l = env()->NewString(jstr.c_str(), jsize(jstr.length())); + } + + void valueArg(value_t* v, const wchar_t* a) + { + auto jstr = toJString(a, std::wcslen(a)); + ((jvalue*) v)->l = env()->NewString(jstr.c_str(), jsize(jstr.length())); + } + +#endif + + template <> void cleanupArg(value_t* v) + { + env()->DeleteLocalRef(((jvalue*) v)->l); + } + + template <> void cleanupArg(value_t* v) + { + env()->DeleteLocalRef(((jvalue*) v)->l); + } + + long getArrayLength(jarray array) + { + return env()->GetArrayLength(array); + } + } +} + diff --git a/src/external/jnipp/jnipp.h b/src/external/jnipp/jnipp.h new file mode 100644 index 000000000..53e12719e --- /dev/null +++ b/src/external/jnipp/jnipp.h @@ -0,0 +1,1036 @@ +#ifndef _JNIPP_H_ +#define _JNIPP_H_ 1 + +// Standard Dependencies +#include +#include // For std::runtime_error +#include + +// Forward Declarations +struct JNIEnv_; +struct _JNIEnv; +struct JavaVM_; +struct _JavaVM; +struct _jmethodID; +struct _jfieldID; +class _jobject; +class _jclass; +class _jarray; + +namespace jni +{ + // JNI Base Types +#ifdef __ANDROID__ + typedef _JNIEnv JNIEnv; + typedef _JavaVM JavaVM; +#else + typedef JNIEnv_ JNIEnv; + typedef JavaVM_ JavaVM; +#endif + + typedef _jobject* jobject; + typedef _jclass* jclass; + typedef _jarray* jarray; + + /** + You can save a method via its handle using Class::getMethod() if it is + going to be used often. This saves Object::call() from having to locate + the method each time by name. + + Note that these handles are global and do not need to be deleted. + */ + typedef _jmethodID* method_t; + + /** + You can save a field via its handle using Class::getField() if it is + going to be used often. This saves Object::set() and Object::get() from + having to locate the field each time by name. + + Note that these handles are global and do not need to be deleted. + */ + typedef _jfieldID* field_t; + + /** + Type used to denote the Java byte type. + */ + typedef unsigned char byte_t; + +#ifdef JNIPP_EXCEPTION_CLASS + + /** + Base class for thrown Exceptions. + */ + typedef JNIPP_EXCEPTION_CLASS Exception; + +#else + + /** + Base class for thrown Exceptions. + */ + typedef std::runtime_error Exception; + +#endif // JNIPP_EXCEPTION_CLASS + + // Foward Declarations + class Object; + + /** + This namespace is for messy implementation details only. It is not a part + of the external API and is subject to change at any time. It is only in a + header file due to the fact it is required by some template functions. + + Long story short... this stuff be messy, yo. + */ + namespace internal + { + /* + Signature Generation + */ + + inline std::string valueSig(const void*) { return "V"; } + inline std::string valueSig(const bool*) { return "Z"; } + inline std::string valueSig(const byte_t*) { return "B"; } + inline std::string valueSig(const wchar_t*) { return "C"; } + inline std::string valueSig(const short*) { return "S"; } + inline std::string valueSig(const int*) { return "I"; } + inline std::string valueSig(const long long*) { return "J"; } + inline std::string valueSig(const float*) { return "F"; } + inline std::string valueSig(const double*) { return "D"; } + inline std::string valueSig(const std::string*) { return "Ljava/lang/String;"; } + inline std::string valueSig(const std::wstring*) { return "Ljava/lang/String;"; } + inline std::string valueSig(const char* const*) { return "Ljava/lang/String;"; } + inline std::string valueSig(const wchar_t* const*) { return "Ljava/lang/String;"; } + std::string valueSig(const Object* obj); + inline std::string valueSig(const Object* const* obj) { return valueSig(obj ? *obj : nullptr); } + + template + inline std::string valueSig(const TArg(*arg)[n]) { return valueSig((const TArg* const*)arg); } + + inline std::string sig() { return ""; } + + template + std::string sig(const TArg& arg, const TArgs&... args) { + return valueSig(&arg) + sig(args...); + } + + /* + Argument Conversion + */ + + typedef long long value_t; + + void valueArg(value_t* v, bool a); + void valueArg(value_t* v, byte_t a); + void valueArg(value_t* v, wchar_t a); + void valueArg(value_t* v, short a); + void valueArg(value_t* v, int a); + void valueArg(value_t* v, long long a); + void valueArg(value_t* v, float a); + void valueArg(value_t* v, double a); + void valueArg(value_t* v, jobject a); + void valueArg(value_t* v, const Object& a); + void valueArg(value_t* v, const Object* const& a); + void valueArg(value_t* v, const std::string& a); + void valueArg(value_t* v, const char* a); + void valueArg(value_t* v, const std::wstring& a); + void valueArg(value_t* v, const wchar_t* a); + + inline void args(value_t*) {} + + template + void args(value_t* values, const TArg& arg, const TArgs&... args) { + valueArg(values, arg); + internal::args(values + 1, args...); + } + + template void cleanupArg(value_t* /* value */) {} + template <> void cleanupArg(value_t* value); + template <> void cleanupArg(value_t* value); + template <> void cleanupArg(value_t* value); + template <> void cleanupArg(value_t* value); + + template + void cleanupArgs(value_t* values) { + cleanupArg(values); + cleanupArgs(values + 1); + } + + template <> + inline void cleanupArgs(value_t* /* values */) {} + + template + class ArgArray + { + public: + ArgArray(const TArgs&... args) { + std::memset(this, 0, sizeof(ArgArray)); + internal::args(values, args...); + } + + ~ArgArray() { + cleanupArgs(values); + } + + value_t values[sizeof...(TArgs)]; + }; + + long getArrayLength(jarray array); + } + + /** + Initialises the Java Native Interface with the given JNIEnv handle, which + gets passed into a native function which is called from Java. This only + needs to be done once per process - further calls are no-ops. + \param env A JNI environment handle. + */ + void init(JNIEnv* env); + /** + Initialises the Java Native Interface with the given JavaVM handle, + which may be accessible. This (or the other overload) only needs to be + done once per process - further calls are no-ops. + \param vm A JNI VM handle. + */ + void init(JavaVM* vm); + + /** + Get the appropriate JNI environment for this thread. + */ + JNIEnv* env(); + + /** + Object corresponds with a `java.lang.Object` instance. With an Object, + you can then call Java methods, and access fields on the Object. To + instantiate an Object of a given class, use the `Class` class. + */ + class Object + { + public: + /** Flags which can be passed to the Object constructor. */ + enum ScopeFlags + { + Temporary = 1, ///< Temporary object. Do not create a global reference. + DeleteLocalInput = 2 ///< The input reference is temporary and can be deleted. + }; + + /** Default constructor. Creates a `null` object. */ + Object() noexcept; + + /** + Copies a reference to another Object. Note that this is not a deep + copy operation, and both Objects will reference the same Java + Object. + \param other The Object to copy. + */ + Object(const Object& other); + + /** + Move constructor. Copies the Object reference from the supplied + Object, and then nulls the supplied Object reference. + \param other The Object to move. + */ + Object(Object&& other) noexcept; + + /** + Creates an Object from a local JNI reference. + \param ref The local JNI reference. + \param scopeFlags Bitmask of ScopeFlags values. + */ + Object(jobject ref, int scopeFlags = 0); + + /** + Destructor. Releases this reference on the Java Object so it can be + picked up by the garbage collector. + */ + virtual ~Object() noexcept; + + /** + Assignment operator. Copies the object reference from the supplied + Object. They will now both point to the same Java Object. + \param other The Object to copy. + \return This Object. + */ + Object& operator=(const Object& other); + + /** + Assignment operator. Moves the object reference from the supplied + Object to this one, and leaves the other one as a null. + \param other The Object to move. + \return This Object. + */ + Object& operator=(Object&& other); + + /** + Tells whether the two Objects refer to the same Java Object. + \param other the Object to compare with. + \return `true` if the same, `false` otherwise. + */ + bool operator==(const Object& other) const; + + /** + Tells whether the two Objects refer to the same Java Object. + \param other the Object to compare with. + \return `true` if the different, `false` otherwise. + */ + bool operator!=(const Object& other) const { return !operator==(other); } + + /** + Calls the given method on this Object. The method should have no + parameters. Note that the return type should be explicitly stated + in the function call. + \param method A method handle which applies to this Object. + \return The method's return value. + */ + template + TReturn call(method_t method) const { return callMethod(method, nullptr); } + + /** + Calls the method on this Object with the given name, and no arguments. + Note that the return type should be explicitly stated in the function + call. + \param name The name of the method to call (with optional signature). + \return The method's return value. + */ + template + TReturn call(const char* name) const { + if (std::strstr(name, "()")) + return call(getMethod(name)); + + // No signature supplied. Generate our own. + method_t method = getMethod(name, ("()" + internal::valueSig((TReturn*) nullptr)).c_str()); + return call(method); + } + + /** + Calls the method on this Object and supplies the given arguments. + Note that the return type should be explicitly stated in the function + call. + \param method The method to call. + \param args Arguments to supply to the method. + \return The method's return value. + */ + template + TReturn call(method_t method, const TArgs&... args) const { + internal::ArgArray transform(args...); + return callMethod(method, transform.values); + } + + /** + Calls the method on this Object and supplies the given arguments. + Note that the return type should be explicitly stated in the function + call. The type signature of the method is calculated by the types of + the supplied arguments. + \param name The name of the method to call (and optional signature). + \param args Arguments to supply to the method. + \return The method's return value. + */ + template + TReturn call(const char* name, const TArgs&... args) const { + if (std::strchr(name, '(')) + return call(getMethod(name), args...); + + std::string sig = "(" + internal::sig(args...) + ")" + internal::valueSig((TReturn*) nullptr); + method_t method = getMethod(name, sig.c_str()); + return call(method, args...); + } + + /** + Gets a field value from this Object. The field must belong to the + Object's class. Note that the field type should be explicitly stated + in the function call. + \param field Identifier for the field to retrieve. + \return The field's value. + */ + template + TType get(field_t field) const; + + /** + Gets a field value from this Object. The field must belong to the + Object's class. Note that the field type should be explicitly stated + in the function call. + \param name The name of the field to retrieve. + \return The field's value. + */ + template + TType get(const char* name) const { + field_t field = getField(name, internal::valueSig((TType*) nullptr).c_str()); + return get(field); + } + + /** + Sets a field's value on this Object. The field must belong to the + Object's class, and the parameter's type should correspond to the + type of the field. + \param field The field to set the value to. + \param value The value to set. + */ + template + void set(field_t field, const TType& value); + + /** + Sets a field's value on this Object. The field must belong to the + Object's class, and the parameter's type should correspond to the + type of the field. + \param name The name of the field to set the value to. + \param value The value to set. + */ + template + void set(const char* name, const TType& value) { + field_t field = getField(name, internal::valueSig((TType*) nullptr).c_str()); + set(field, value); + } + + /** + Tells whether this Object is currently a `null` pointer. + \return `true` if `null`, `false` if it references an object. + */ + bool isNull() const noexcept; + + /** + Gets a handle for this Object's class. Ideally, this should just return a Class, + but C++ won't let us do that that. + \return The Object's Class's handle. + */ + jclass getClass() const; + + /** + Gets the underlying JNI jobject handle. + \return The JNI handle. + */ + jobject getHandle() const noexcept { return _handle; } + + /** + Create a local reference for the underlying JNI handle. + \return The local reference. + */ + jobject makeLocalReference() const; + + private: + // Helper Functions + method_t getMethod(const char* name, const char* signature) const; + method_t getMethod(const char* nameAndSignature) const; + field_t getField(const char* name, const char* signature) const; + template TType callMethod(method_t method, internal::value_t* values) const; + + // Instance Variables + jobject _handle; + mutable jclass _class; + bool _isGlobal; + }; + + /** + Class corresponds with `java.lang.Class`, and allows you to instantiate + Objects and get class members such as methods and fields. + */ + class Class : protected Object + { + public: + /** + Creates a null class reference. + */ + Class() : Object() {} + + /** + Obtains a class reference to the Java class with the given qualified + name. + \param name The qualified class name (e.g. "java/lang/String"). + */ + Class(const char* name); + + /** + Creates a Class object by JNI reference. + \param ref The JNI class reference. + \param scopeFlags Bitmask of Object::ScopeFlags. + */ + Class(jclass ref, int scopeFlags = Temporary); + + /** + Tells whether this Class is null or valid. + \return `true` if null, `false` if valid. + */ + bool isNull() const noexcept { return Object::isNull(); } + + /** + Creates a new instance of this Java class and returns a reference to + it. The item's parameterless constructor is called. + \return The created instance. + */ + Object newInstance() const; + + /** + Creates a new instance of this Java class and returns a reference to + it. + \param constructor The constructor to call. + \param args Arguments to supply to the constructor. + \return The created instance. + */ + template + Object newInstance(method_t constructor, const TArgs&... args) const { + internal::ArgArray transform(args...); + return newObject(constructor, transform.values); + } + + /** + Creates a new instance of this Java class and returns a reference to + it. The constructor signature is determined by the supplied parameters, + and the parameters are then passed to the constructor. + \param args Arguments to supply to the constructor. + \return The created instance. + */ + template + Object newInstance(const TArgs&... args) const { + method_t constructor = getMethod("", ("(" + internal::sig(args...) + ")V").c_str()); + return newInstance(constructor, args...); + } + + /** + Gets a handle to the field with the given name and type signature. + This handle can then be stored so that the field does not need to + be looked up by name again. It does not need to be deleted. + \param name The name of the field. + \param signature The JNI type signature of the field. + \return The field ID. + */ + field_t getField(const char* name, const char* signature) const; + + /** + Gets a handle to the field with the given name and the supplied type. + This handle can then be stored so that the field does not need to + be looked up by name again. It does not need to be deleted. + \param name The name of the field. + \return The field ID. + */ + template + field_t getField(const char* name) const { + return getField(name, internal::valueSig((TType*) nullptr).c_str()); + } + + /** + Gets a handle to the static field with the given name and type signature. + This handle can then be stored so that the field does not need to + be looked up by name again. It does not need to be deleted. + \param name The name of the field. + \param signature The JNI type signature of the field. + \return The field ID. + */ + field_t getStaticField(const char* name, const char* signature) const; + + /** + Gets a handle to the static field with the given name and the supplied type. + This handle can then be stored so that the field does not need to + be looked up by name again. It does not need to be deleted. + \param name The name of the field. + \return The field ID. + */ + template + field_t getStaticField(const char* name) const { + return getStaticField(name, internal::valueSig((TType*)nullptr).c_str()); + } + + /** + Gets a handle to the method with the given name and signature. + This handle can then be stored so that the method does not need + to be looked up by name again. It does not need to be deleted. + \param name The name of the method. + \param signature The JNI method signature. + \return The method ID. + */ + method_t getMethod(const char* name, const char* signature) const; + + /** + Gets a handle to the method with the given name and signature. + This handle can then be stored so that the method does not need + to be looked up by name again. It does not need to be deleted. + \param nameAndSignature Name and signature identifier (e.g. "toString()Ljava/lang/String;"). + \return The method ID. + */ + method_t getMethod(const char* nameAndSignature) const; + + /** + Gets a handle to the static method with the given name and signature. + This handle can then be stored so that the method does not need + to be looked up by name again. It does not need to be deleted. + \param name The name of the method. + \param signature The JNI method signature. + \return The method ID. + */ + method_t getStaticMethod(const char* name, const char* signature) const; + + /** + Gets a handle to the static method with the given name and signature. + This handle can then be stored so that the method does not need + to be looked up by name again. It does not need to be deleted. + \param nameAndSignature Name and signature identifier (e.g. "toString()Ljava/lang/String;"). + \return The method ID. + */ + method_t getStaticMethod(const char* nameAndSignature) const; + + /** + Gets a handle to the constructor for this Class with the given + signature. Note that the return type should always be `void` ("V"). + \param signature The JNI method signature for the constructor. + \return The constructor method ID. + */ + method_t getConstructor(const char* signature) const { return getMethod("", signature); } + + /** + Gets the parent Class of this Class. + \return The parent class. + */ + Class getParent() const; + + /** + Gets the JNI-qualified name of this Class. + \return The Class name. + */ + std::string getName() const; + + /** + Calls a static method on this Class. The method should have no + parameters. Note that the return type should be explicitly stated + in the function call. + \param method A method handle which applies to this Object. + \return The method's return value. + */ + template + TReturn call(method_t method) const { return callStaticMethod(method, nullptr); } + + /** + Calls a static method on this Class with the given name, and no arguments. + Note that the return type should be explicitly stated in the function + call. + \param name The name of the method to call. + \return The method's return value. + */ + template + TReturn call(const char* name) const { + method_t method = getStaticMethod(name, ("()" + internal::valueSig((TReturn*) nullptr)).c_str()); + return call(method); + } + + /** + Calls a static method on this Class and supplies the given arguments. + Note that the return type should be explicitly stated in the function + call. + \param method The method to call. + \param args Arguments to supply to the method. + \return The method's return value. + */ + template + TReturn call(method_t method, const TArgs&... args) const { + internal::ArgArray transform(args...); + return callStaticMethod(method, transform.values); + } + + /** + Calls a static method on this Class and supplies the given arguments. + Note that the return type should be explicitly stated in the function + call. The type signature of the method is calculated by the types of + the supplied arguments. + \param name The name of the method to call. + \param args Arguments to supply to the method. + \return The method's return value. + */ + template + TReturn call(const char* name, const TArgs&... args) const { + if (std::strchr(name, '(')) + return call(getStaticMethod(name), args...); + + std::string sig = "(" + internal::sig(args...) + ")" + internal::valueSig((TReturn*) nullptr); + method_t method = getStaticMethod(name, sig.c_str()); + return call(method, args...); + } + + /** + Calls a non-static method on this Class, applying it to the supplied + Object. The difference between this and Object.call() is that the + specific class implementation of the method is called, rather than + doing a virtual method lookup. + \param obj The Object to call the method on. + \param method The method to call. + \return The method's return value. + */ + template + TReturn call(const Object& obj, method_t method) const { + return callExactMethod(obj.getHandle(), method, nullptr); + } + + /** + Calls a non-static method on this Class, applying it to the supplied + Object. The difference between this and Object.call() is that the + specific class implementation of the method is called, rather than + doing a virtual method lookup. + \param obj The Object to call the method on. + \param name The name of the method to call. + \return The method's return value. + */ + template + TReturn call(const Object& obj, const char* name) const { + method_t method = getMethod(name, ("()" + internal::valueSig((TReturn*) nullptr)).c_str()); + return call(obj, method); + } + template + TReturn call(const Object* obj, const char* name) const { + return call(obj, name); + } + + /** + Calls a non-static method on this Class, applying it to the supplied + Object. The difference between this and Object.call() is that the + specific class implementation of the method is called, rather than + doing a virtual method lookup. + \param obj The Object to call the method on. + \param method The method to call. + \param args Arguments to pass to the method. + \return The method's return value. + */ + template + TReturn call(const Object& obj, method_t method, const TArgs&... args) const { + internal::ArgArray transform(args...); + return callExactMethod(obj.getHandle(), method, transform.values); + } + template + TReturn call(const Object* obj, method_t method, const TArgs&... args) const { + return call(*obj, method, args...); + } + + /** + Calls a non-static method on this Class, applying it to the supplied + Object. The difference between this and Object.call() is that the + specific class implementation of the method is called, rather than + doing a virtual method lookup. + \param obj The Object to call the method on. + \param name The name of the method to call. + \param args Arguments to pass to the method. + \return The method's return value. + */ + template + TReturn call(const Object& obj, const char* name, const TArgs&... args) const { + std::string sig = "(" + internal::sig(args...) + ")" + internal::valueSig((TReturn*) nullptr); + method_t method = getMethod(name, sig.c_str()); + return call(obj, method, args...); + } + template + TReturn call(const Object* obj, const char* name, const TArgs&... args) const { + return call(*obj, name, args...); + } + + /** + Gets a static field value from this Class. Note that the field type + should be explicitly stated in the function call. + \param field Identifier for the field to retrieve. + \return The field's value. + */ + template + TType get(field_t field) const; + + /** + Gets a static field value from this Class. Note that the field type + should be explicitly stated in the function call. + \param name The name of the field to retrieve. + \return The field's value. + */ + template + TType get(const char* name) const { + field_t field = getStaticField(name, internal::valueSig((TType*) nullptr).c_str()); + return get(field); + } + + /** + Sets a static field's value on this Class. The parameter's type should + correspond to the type of the field. + \param field The field to set the value to. + \param value The value to set. + */ + template + void set(field_t field, const TType& value); + + /** + Sets a static field's value on this Class. The parameter's type + should correspond to the type of the field. + \param name The name of the field to set the value to. + \param value The value to set. + */ + template + void set(const char* name, const TType& value) { + field_t field = getStaticField(name, internal::valueSig((TType*) nullptr).c_str()); + set(field, value); + } + + /** + Gets the underlying JNI jclass handle. + \return The JNI handle. + */ + jclass getHandle() const noexcept { return jclass(Object::getHandle()); } + + private: + // Helper Functions + template TType callStaticMethod(method_t method, internal::value_t* values) const; + template TType callExactMethod(jobject obj, method_t method, internal::value_t* values) const; + Object newObject(method_t constructor, internal::value_t* args) const; + }; + + /** + Convenience class for dealing with Java enums. + */ + class Enum : protected Class + { + public: + /** + Loads the Enum with the given JNI-formatted name. + \param name The name of the enum. + */ + Enum(const char* name); + + /** + Gets the enum value with the given name. + \param name The name of the enum value. + \return The enum value identifier. + */ + Object get(const char* name) const; + + private: + // Instance Variables + std::string _name; + }; + + /** + Used to interact with native Java arrays. The element type can be any primitive + type, jni::Object, std::string or std::wstring. + */ + template + class Array : public Object + { + public: + /** + Default constructor. Creates a null array reference. + */ + Array() noexcept; + + /** + Creates a Array object by JNI reference. + \param ref The JNI array reference. + \param scopeFlags Bitmask of Object::ScopeFlags. + */ + Array(jarray ref, int scopeFlags = Temporary); + + /** + Creates an Array of the given length. The elements are default initialised + to zero / null values. + \param length The Array length. + */ + Array(long length); + + /** + Creates an Array of the given length. A Class type is also specified, but can + be left null to default to "java.lang.Object". + \param length The Array length. + \param type The element type. + + */ + Array(long length, const Class& type); + + /** + Copy constructor. Shares a reference to the Java array with the + copied Array object. + \param other The Array to copy. + */ + Array(const Array& other); + + /** + Move constructor. Moves the array reference to the new Array, and leaves + the previous Array as a null reference. + \param other The Array to move. + */ + Array(Array&& other) noexcept; + + /** + Assignment operator. Copies the array reference from the supplied + Array. They will now both point to the same Java array. + \param other The Array to copy. + \return This Array. + */ + Array& operator=(const Array& other); + + /** + Assignment operator. Moves the array reference from the supplied + Array to this one, and leaves the other one as a null. + \param other The Array to move. + \return This Array. + */ + Array& operator=(Array&& other); + + /** + Checks whether these Arrays both reference the same java array. + \param other The Array to compare with. + \return true if the same (or both null), false otherwise. + */ + bool operator==(const Array& other) const { return Object::operator==(other); } + + /** + Checks whether these Arrays reference different the java arrays. + \param other The Array to compare with. + \return false if the same (or both null), true otherwise. + */ + bool operator!=(const Array& other) const { return !operator==(other); } + + /** + Sets the element value at the given index in the Array. + \param index The zero-based index. + \param value The value to set. + */ + void setElement(long index, TElement value); + + /** + Gets the value at the given index within the Array. + \param index The zero-based index. + \return The element at the given index. + */ + TElement getElement(long index) const; + TElement operator[](long index) const { return getElement(index); } + + /** + Gets the length of this Array. + \return The array length. + */ + long getLength() const; + + /** + Gets the underlying JNI jarray handle. + \return The JNI handle. + */ + jarray getHandle() const noexcept { return jarray(Object::getHandle()); } + + private: + // Instance Variables + mutable long _length; ///< Mutable as it may only finally get set in a getLength() call. + }; + + /** + When the application's entry point is in C++ rather than in Java, it will + need to spin up its own instance of the Java Virtual Machine (JVM) before + it can initialize the Java Native Interface. Vm is used to create and + destroy a running JVM instance. + + It uses the RAII idiom, so when the destructor is called, the Vm is shut + down. + + Note that currently only one instance is supported. Attempts to create + more will result in an InitializationException. + */ + class Vm final + { + public: + /** + Starts the Java Virtual Machine. + \param path The path to the jvm.dll (or null for auto-detect). + */ + Vm(const char* path = nullptr); + + /** Destroys the running instance of the JVM. */ + ~Vm(); + }; + + /** + A Java method call threw an Exception. + */ + class InvocationException : public Exception + { + public: + /** + Constructor with an error message. + \param msg Message to pass to the Exception. + */ + InvocationException(const char* msg = "Java Exception detected") : Exception(msg) {} + }; + + /** + A supplied name or type signature could not be resolved. + */ + class NameResolutionException : public Exception + { + public: + /** + Constructor with an error message. + \param name The name of the unresolved symbol. + */ + NameResolutionException(const char* name) : Exception(name) {} + }; + + /** + The Java Native Interface was not properly initialized. + */ + class InitializationException : public Exception + { + public: + /** + Constructor with an error message. + \param msg Message to pass to the Exception. + */ + InitializationException(const char* msg) : Exception(msg) {} + }; + + /* + Array Implementation + */ + + template + Array::Array() noexcept : Object(), _length(0) + { + } + + template + Array::Array(jarray ref, int scopeFlags) : Object((jobject) ref, scopeFlags), _length(-1) + { + } + + template + Array::Array(const Array& other) : Object(other), _length(other._length) + { + } + + template + Array::Array(Array&& other) noexcept : Object((Object&&)other), _length(other._length) + { + other._length = 0; + } + + template + Array& Array::operator=(const Array& other) + { + if (&other != this) + { + Object::operator=(other); + _length = other._length; + } + + return *this; + } + + template + Array& Array::operator=(Array&& other) + { + if (&other != this) + { + Object::operator=((Object&&) other); + _length = other._length; + + other._length = 0; + } + + return *this; + } + + template + long Array::getLength() const + { + if (_length < 0) + { + _length = internal::getArrayLength(getHandle()); + } + + return _length; + } +} + +#endif // _JNIPP_H_ + diff --git a/src/external/jnipp/jnipp.sln b/src/external/jnipp/jnipp.sln new file mode 100644 index 000000000..7747be98c --- /dev/null +++ b/src/external/jnipp/jnipp.sln @@ -0,0 +1,45 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnipp", "jnipp.vcxproj", "{A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{5A4C5782-6F86-4413-9E19-3F6539DCD835}" + ProjectSection(SolutionItems) = preProject + LICENSE = LICENSE + makefile = makefile + README.md = README.md + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests\tests.vcxproj", "{911DB449-3D76-4261-8FFD-9BA508ED1D5B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x64.ActiveCfg = Debug|x64 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x64.Build.0 = Debug|x64 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x86.ActiveCfg = Debug|Win32 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Debug|x86.Build.0 = Debug|Win32 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x64.ActiveCfg = Release|x64 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x64.Build.0 = Release|x64 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x86.ActiveCfg = Release|Win32 + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58}.Release|x86.Build.0 = Release|Win32 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x64.ActiveCfg = Debug|x64 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x64.Build.0 = Debug|x64 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x86.ActiveCfg = Debug|Win32 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Debug|x86.Build.0 = Debug|Win32 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x64.ActiveCfg = Release|x64 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x64.Build.0 = Release|x64 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x86.ActiveCfg = Release|Win32 + {911DB449-3D76-4261-8FFD-9BA508ED1D5B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/external/jnipp/jnipp.vcxproj b/src/external/jnipp/jnipp.vcxproj new file mode 100644 index 000000000..56fb97090 --- /dev/null +++ b/src/external/jnipp/jnipp.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {A32D4B7D-7009-4C87-9DE8-D7DB659F5B58} + jnipp + 10.0 + + + + StaticLibrary + true + v142 + MultiByte + + + StaticLibrary + false + v142 + true + MultiByte + + + StaticLibrary + true + v142 + MultiByte + + + StaticLibrary + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 + + + jvm.lib + + + $(JAVA_HOME)\lib + + + + + Level3 + Disabled + true + $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 + + + jvm.lib + + + $(JAVA_HOME)\lib + + + + + Level3 + MaxSpeed + true + true + true + $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 + + + true + true + + + jvm.lib + + + $(JAVA_HOME)\lib + + + + + Level3 + MaxSpeed + true + true + true + $(JAVA_HOME)\include;$(JAVA_HOME)\include\win32 + + + true + true + + + jvm.lib + + + $(JAVA_HOME)\lib + + + + + + + + + + + + \ No newline at end of file diff --git a/src/external/jnipp/jnipp.vcxproj.filters b/src/external/jnipp/jnipp.vcxproj.filters new file mode 100644 index 000000000..ec6901cb3 --- /dev/null +++ b/src/external/jnipp/jnipp.vcxproj.filters @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/external/jnipp/makefile b/src/external/jnipp/makefile new file mode 100644 index 000000000..8c069e819 --- /dev/null +++ b/src/external/jnipp/makefile @@ -0,0 +1,32 @@ +CC=g++ + +OS_NAME := linux + +ifeq ($(OS),Windows_NT) + OS_NAME := win32 + RM := del +else + RM := rm -f + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + OS_NAME := darwin + endif +endif + +JAVA_HOME ?= /usr/lib/jvm/default-java + +CXXFLAGS=-I. -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/$(OS_NAME) -ldl -std=c++11 + +SRC=jnipp.o main.o +VPATH=tests + +%.o: %.cpp + $(CC) -c -o $@ $< $(CXXFLAGS) + +test: $(SRC) + $(CC) -o test $(SRC) $(CXXFLAGS) + +clean: + -$(RM) $(SRC) test + +.PHONY: clean diff --git a/src/external/jnipp/tests/main.cpp b/src/external/jnipp/tests/main.cpp new file mode 100644 index 000000000..96de84296 --- /dev/null +++ b/src/external/jnipp/tests/main.cpp @@ -0,0 +1,619 @@ +// Project Dependencies +#include + +// Standard Dependencies +#include + +// Local Dependencies +#include "testing.h" + +/* + jni::Vm Tests + */ + + +TEST(Vm_detectsJreInstall) +{ + try + { + jni::Vm vm; + } + catch (jni::InitializationException&) + { + ASSERT(0); + return; + } + + ASSERT(1); +} + +TEST(Vm_notAllowedMultipleVms) +{ + try + { + jni::Vm firstVm; + jni::Vm secondVm; // Throws an exception. + } + catch (jni::InitializationException&) + { + ASSERT(1); + return; + } + + ASSERT(0); +} + +/* + jni::Class Tests + */ + + +TEST(Class_findByName_success) +{ + jni::Class cls("java/lang/String"); + + ASSERT(!cls.isNull()); +} + +TEST(Class_findByName_failure) +{ + try + { + jni::Class cls("does/not/Exist"); + } + catch (jni::NameResolutionException&) + { + ASSERT(1); + return; + } + + ASSERT(0); +} + +TEST(Class_getName) +{ + jni::Class cls("java/lang/String"); + + ASSERT(cls.getName() == "java.lang.String"); +} + +TEST(Class_getParent) +{ + jni::Class parent = jni::Class("java/lang/Integer").getParent(); + + ASSERT(parent.getName() == "java.lang.Number"); +} + +TEST(Class_newInstance) +{ + jni::Class Integer("java/lang/Integer"); + jni::method_t constructor = Integer.getConstructor("(Ljava/lang/String;)V"); + jni::Object i = Integer.newInstance(constructor, "123"); + jni::Object str = jni::Class("java/lang/String").newInstance(); + + ASSERT(!i.isNull()); + ASSERT(!str.isNull()); +} + +TEST(Class_newInstance_withArgs) +{ + jni::Object str1 = jni::Class("java/lang/String").newInstance("Testing..."); + jni::Object str2 = jni::Class("java/lang/String").newInstance(L"Testing..."); + + ASSERT(!str1.isNull()); + ASSERT(!str2.isNull()); +} + +TEST(Class_getStaticField) +{ + jni::field_t field = jni::Class("java/lang/Integer").getStaticField("MAX_VALUE", "I"); + + ASSERT(field); +} + +TEST(Class_getMethod) +{ + jni::method_t method1 = jni::Class("java/lang/Integer").getMethod("intValue", "()I"); + jni::method_t method2 = jni::Class("java/lang/Integer").getMethod("intValue()I"); + + ASSERT(method1); + ASSERT(method2); +} + +TEST(Class_getStaticMethod) +{ + jni::method_t method = jni::Class("java/lang/Integer").getStaticMethod("compare", "(II)I"); + + ASSERT(method); +} + +TEST(Class_call) +{ + jni::Class Integer("java/lang/Math"); + + double x = Integer.call("random"); + + ASSERT(x >= 0.0); + ASSERT(x < 1.0); +} + +TEST(Class_get_staticField) +{ + jni::Class Integer("java/lang/Integer"); + jni::field_t field = Integer.getStaticField("SIZE", "I"); + + ASSERT(Integer.get(field) == 32); +} + +TEST(Class_get_staticField_byName) +{ + jni::Class Integer("java/lang/Integer"); + jni::Class Character("java/lang/Character"); + jni::Class Short("java/lang/Short"); + jni::Class Long("java/lang/Long"); + jni::Class Float("java/lang/Float"); + jni::Class Double("java/lang/Double"); + + ASSERT(Short.get("MAX_VALUE") == short(0x7FFF)); + ASSERT(Character.get("MAX_VALUE") == L'\xFFFF') + ASSERT(Integer.get("MAX_VALUE") == int(0x7FFFFFFF)); + ASSERT(Long.get("MAX_VALUE") == (long long) (0x7FFFFFFFFFFFFFFF)); + ASSERT(std::isnan(Float.get("NaN"))); + ASSERT(std::isnan(Double.get("NaN"))); +} + +TEST(Class_getConstructor) +{ + jni::Class Integer("java/lang/Integer"); + jni::method_t constructor = Integer.getConstructor("(Ljava/lang/String;)V"); + + ASSERT(constructor); +} + +TEST(Class_call_staticMethod) +{ + jni::Class Integer("java/lang/Integer"); + jni::method_t method = Integer.getStaticMethod("parseInt", "(Ljava/lang/String;)I"); + + int i = Integer.call(method, "1000"); + + ASSERT(i == 1000); +} + +TEST(Class_call_staticMethod_byName) +{ + int i = jni::Class("java/lang/Integer").call("parseInt", "1000"); + bool b = jni::Class("java/lang/Boolean").call("parseBoolean", "true"); + wchar_t c = jni::Class("java/lang/Character").call("toLowerCase", L'X'); + short s = jni::Class("java/lang/Short").call("parseShort", "1000"); + long long l = jni::Class("java/lang/Long").call("parseLong", "1000"); + float f = jni::Class("java/lang/Float").call("parseFloat", "123.0"); + double d = jni::Class("java/lang/Double").call("parseDouble", "123.0"); + + ASSERT(i == 1000); + ASSERT(b == true); + ASSERT(c == L'x'); + ASSERT(s == 1000); + ASSERT(l == 1000); + ASSERT(f == 123.0); // Warning: floating point comparison. + ASSERT(d == 123.0); // Warning: floating point comparison. +} + +/* + jni::Object Tests + */ + + +TEST(Object_defaultConstructor_isNull) +{ + jni::Object o; + + ASSERT(o.isNull()); +} + +TEST(Object_copyConstructorIsSameObject) +{ + jni::Object a = jni::Class("java/lang/String").newInstance(); + jni::Object b = a; + + ASSERT(a == b); +} + +TEST(Object_moveConstructor) +{ + jni::Object a = jni::Class("java/lang/String").newInstance(); + jni::Object b = std::move(a); + + ASSERT(a.isNull()); + ASSERT(!b.isNull()); +} + +TEST(Object_copyAssignmentOperator) +{ + jni::Object a = jni::Class("java/lang/String").newInstance(); + jni::Object b = jni::Class("java/lang/Integer").newInstance(0); + + a = b; + + ASSERT(a == b); +} + +TEST(Object_moveAssignmentOperator) +{ + jni::Object a = jni::Class("java/lang/String").newInstance(); + jni::Object b = jni::Class("java/lang/Integer").newInstance(0); + + a = std::move(b); + + ASSERT(!a.isNull()); + ASSERT(b.isNull()); +} + +TEST(Object_call) +{ + jni::Class Integer("java/lang/Integer"); + jni::method_t intValue = Integer.getMethod("intValue", "()I"); + jni::Object i = Integer.newInstance(100); + + ASSERT(i.call(intValue) == 100); +} + +TEST(Object_call_byName) +{ + jni::Object i = jni::Class("java/lang/Integer").newInstance(100); + jni::Object b = jni::Class("java/lang/Boolean").newInstance(true); + jni::Object s = jni::Class("java/lang/Short").newInstance(short(100)); + jni::Object l = jni::Class("java/lang/Long").newInstance(100LL); + jni::Object f = jni::Class("java/lang/Float").newInstance(100.0f); + jni::Object d = jni::Class("java/lang/Double").newInstance(100.0); + + ASSERT(i.call("intValue") == 100); + ASSERT(s.call("shortValue") == 100); + ASSERT(b.call("booleanValue") == true); + ASSERT(l.call("longValue") == 100LL); + ASSERT(f.call("floatValue") == 100.0f); // Warning: Floating point comparison. + ASSERT(d.call("doubleValue") == 100.0); // Warning: Floating point comparison. + ASSERT(i.call("toString") == L"100"); + ASSERT(i.call("toString") == "100"); +} + +TEST(Object_call_withArgs) +{ + jni::Class String("java/lang/String"); + jni::method_t charAt = String.getMethod("charAt", "(I)C"); + jni::Object str = String.newInstance("Testing"); + + ASSERT(str.call(charAt, 1) == L'e'); +} + +TEST(Object_call_byNameWithArgs) +{ + jni::Object str = jni::Class("java/lang/String").newInstance("Testing"); + jni::Object str2 = jni::Class("java/lang/String").newInstance(L"Testing"); + + ASSERT(str.call("charAt", 1) == L'e'); + ASSERT(str2.call("charAt", 1) == L'e'); +} + +TEST(Object_makeLocalReference) +{ + jni::Object str = jni::Class("java/lang/String").newInstance("Testing"); + + jni::jobject local = str.makeLocalReference(); + ASSERT(local != nullptr); + ASSERT(local != str.getHandle()); + + jni::Object fromLocal(local, jni::Object::DeleteLocalInput); + ASSERT(!fromLocal.isNull()); + ASSERT(str == fromLocal); +} + +/* + jni::Enum Tests + */ + + +TEST(Enum_get) +{ + jni::Class Thread("java/lang/Thread"); + jni::Enum State("java/lang/Thread$State"); + jni::method_t currentThread = Thread.getStaticMethod("currentThread", "()Ljava/lang/Thread;"); + jni::method_t getState = Thread.getMethod("getState", "()Ljava/lang/Thread$State;"); + + jni::Object thread = Thread.call(currentThread); + jni::Object state = thread.call(getState); + + ASSERT(state == State.get("RUNNABLE")); +} + +/* + jni::Array Tests + */ + +TEST(Array_defaultConstructor) +{ + jni::Array a; + + ASSERT(a.getLength() == 0); + ASSERT(a.isNull()); +} + +TEST(Array_constructor) +{ + jni::Array a(10); + + ASSERT(a.getLength() == 10); + ASSERT(!a.isNull()); +} + +TEST(Array_constructor_eachType) +{ + jni::Array b(10); + jni::Array c(10); + jni::Array s(10); + jni::Array i(10); + jni::Array l(10); + jni::Array f(10); + jni::Array d(10); + jni::Array str(10); + jni::Array wstr(10); + jni::Array obj(10); + jni::Array obj2(10, jni::Class("java/lang/Integer")); + + jni::Object* objs[] = { &b, &c, &s, &i, &l, &f, &d, &str, &wstr, &obj, &obj2 }; + + for (auto o : objs) + ASSERT(!o->isNull()); +} + +TEST(Array_constructor_withType) +{ + jni::Array a(10, jni::Class("java/lang/Integer")); + + ASSERT(a.getLength() == 10); + ASSERT(!a.isNull()); +} + +TEST(Array_copyConstructor) +{ + jni::Array a(10); + jni::Array b = a; + + ASSERT(a == b); +} + +TEST(Array_moveConstructor) +{ + jni::Array a(10); + jni::Array b = std::move(a); + + ASSERT(a.isNull()); + ASSERT(!b.isNull()); +} + +TEST(Array_copyAssignmentOperator) +{ + jni::Array a(10); + jni::Array b(11); + + a = b; + + ASSERT(a == b); +} + +TEST(Array_moveAssignmentOperator) +{ + jni::Array a(10); + jni::Array b(11); + + a = std::move(b); + + ASSERT(!a.isNull()); + ASSERT(b.isNull()); +} + +TEST(Array_getElement_defaultValue) +{ + jni::Array a(10); + jni::Array s(10); + + ASSERT(a.getElement(0) == 0); + ASSERT(s.getElement(0).length() == 0); +} + +TEST(Array_getElement_indexException) +{ + jni::Array a(10); + + try + { + int result = a.getElement(1000); + ASSERT(0); + } + catch (jni::Exception&) + { + ASSERT(1); + } +} + +TEST(Array_setElement_basicType) +{ + jni::Array a(10); + + for (int i = 0; i < 10; i++) + a.setElement(i, i); + + for (int i = 0; i < 10; i++) + ASSERT(a.getElement(i) == i); +} + +TEST(Array_setElement_string) +{ + jni::Array a(10); + + for (int i = 0; i < 10; i++) + a.setElement(i, std::to_wstring(i)); + + for (int i = 0; i < 10; i++) + ASSERT(a.getElement(i) == std::to_wstring(i)); +} + +TEST(Array_setElement_indexException) +{ + jni::Array s(10); + + try + { + auto result = s.getElement(1000); + ASSERT(0); + } + catch (jni::Exception&) + { + ASSERT(1); + } +} + +/* + Argument Type Tests + */ + + +TEST(Arg_bool) +{ + std::string str1 = jni::Class("java/lang/String").call("valueOf", true); + std::wstring str2 = jni::Class("java/lang/String").call("valueOf", true); + + ASSERT(str1 == "true"); + ASSERT(str2 == L"true"); +} + +TEST(Arg_wchar) +{ + std::string str1 = jni::Class("java/lang/String").call("valueOf", L'X'); + std::wstring str2 = jni::Class("java/lang/String").call("valueOf", L'X'); + + ASSERT(str1 == "X"); + ASSERT(str2 == L"X"); +} + +TEST(Arg_double) +{ + std::string str1 = jni::Class("java/lang/String").call("valueOf", 123.0); + std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123.0); + + ASSERT(str1 == "123.0"); + ASSERT(str2 == L"123.0"); +} + +TEST(Arg_float) +{ + std::string str1 = jni::Class("java/lang/String").call("valueOf", 123.0f); + std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123.0f); + + ASSERT(str1 == "123.0"); + ASSERT(str2 == L"123.0"); +} + +TEST(Arg_int) +{ + std::string str1 = jni::Class("java/lang/String").call("valueOf", 123); + std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123); + + ASSERT(str1 == "123"); + ASSERT(str2 == L"123"); +} + +TEST(Arg_longLong) +{ + std::string str1 = jni::Class("java/lang/String").call("valueOf", 123LL); + std::wstring str2 = jni::Class("java/lang/String").call("valueOf", 123LL); + + ASSERT(str1 == "123"); + ASSERT(str2 == L"123"); +} + +TEST(Arg_Object) +{ + jni::Object str1 = jni::Class("java/lang/String").newInstance("123"); + int i = jni::Class("java/lang/Integer").call("parseInt", str1); + + ASSERT(i == 123); +} + +TEST(Arg_ObjectPtr) +{ + jni::Object str1 = jni::Class("java/lang/String").newInstance("123"); + int i = jni::Class("java/lang/Integer").call("parseInt", &str1); + + ASSERT(i == 123); +} + +int main() +{ + // jni::Vm Tests + RUN_TEST(Vm_detectsJreInstall); + RUN_TEST(Vm_notAllowedMultipleVms); + + { + jni::Vm vm; + + // jni::Class Tests + RUN_TEST(Class_findByName_success); + RUN_TEST(Class_findByName_failure); + RUN_TEST(Class_getName); + RUN_TEST(Class_getParent); + RUN_TEST(Class_newInstance); + RUN_TEST(Class_newInstance_withArgs); + RUN_TEST(Class_getStaticField); + RUN_TEST(Class_getMethod); + RUN_TEST(Class_getStaticMethod); + RUN_TEST(Class_call); + RUN_TEST(Class_get_staticField); + RUN_TEST(Class_get_staticField_byName); + RUN_TEST(Class_call_staticMethod_byName); + RUN_TEST(Class_getConstructor); + + // jni::Object Tests + RUN_TEST(Object_defaultConstructor_isNull); + RUN_TEST(Object_copyConstructorIsSameObject); + RUN_TEST(Object_moveConstructor); + RUN_TEST(Object_copyAssignmentOperator); + RUN_TEST(Object_moveAssignmentOperator); + RUN_TEST(Object_call); + RUN_TEST(Object_call_byName); + RUN_TEST(Object_call_withArgs); + RUN_TEST(Object_call_byNameWithArgs); + RUN_TEST(Object_makeLocalReference); + + // jni::Enum Tests + RUN_TEST(Enum_get); + + // jni::Array Tests + RUN_TEST(Array_defaultConstructor); + RUN_TEST(Array_constructor); + RUN_TEST(Array_constructor_eachType); + RUN_TEST(Array_constructor_withType); + RUN_TEST(Array_copyConstructor); + RUN_TEST(Array_moveConstructor); + RUN_TEST(Array_getElement_defaultValue); + RUN_TEST(Array_getElement_indexException); + RUN_TEST(Array_setElement_basicType); + RUN_TEST(Array_setElement_string); + RUN_TEST(Array_setElement_indexException); + + // Argument Type Tests + RUN_TEST(Arg_bool); + RUN_TEST(Arg_wchar); + RUN_TEST(Arg_double); + RUN_TEST(Arg_float); + RUN_TEST(Arg_int); + RUN_TEST(Arg_longLong); + RUN_TEST(Arg_Object); + RUN_TEST(Arg_ObjectPtr); + } + + std::cout << "Press a key to continue..." << std::endl; + std::cin.get(); + return 0; +} + diff --git a/src/external/jnipp/tests/testing.h b/src/external/jnipp/tests/testing.h new file mode 100644 index 000000000..4d6f6cc97 --- /dev/null +++ b/src/external/jnipp/tests/testing.h @@ -0,0 +1,33 @@ +#ifndef _TESTING_H_ +#define _TESTING_H_ 1 + +// Standard Dependencies +#include +#include + +#ifdef ASSERT +# undef ASSERT +#endif + +/** Run the test with the given name. */ +#define RUN_TEST(TestName) { \ + bool __test_result = true; \ + std::cout << "Executing test " << std::left << std::setw(40) << #TestName; \ + TestName(__test_result); \ + std::cout << "=> " << (__test_result ? "Success" : "Fail") << std::endl; \ +} + +/** Define a test with the given name. */ +#define TEST(TestName) \ + void TestName(bool& __test_result) + +/** Ensures the given condition passes for the test to pass. */ +#define ASSERT(condition) { \ + if (!(condition)) { \ + __test_result = false; \ + return; \ + } \ +} + +#endif // _TESTING_H_ + diff --git a/src/external/jnipp/tests/tests.vcxproj b/src/external/jnipp/tests/tests.vcxproj new file mode 100644 index 000000000..923e3e209 --- /dev/null +++ b/src/external/jnipp/tests/tests.vcxproj @@ -0,0 +1,130 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {911DB449-3D76-4261-8FFD-9BA508ED1D5B} + tests + 10.0 + + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(SolutionDir) + + + + + Level3 + Disabled + true + $(SolutionDir) + + + + + Level3 + MaxSpeed + true + true + true + $(SolutionDir) + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + $(SolutionDir) + + + true + true + + + + + {a32d4b7d-7009-4c87-9de8-d7db659f5b58} + + + + + + + + + + + + \ No newline at end of file diff --git a/src/external/jnipp/tests/tests.vcxproj.filters b/src/external/jnipp/tests/tests.vcxproj.filters new file mode 100644 index 000000000..fdc9feded --- /dev/null +++ b/src/external/jnipp/tests/tests.vcxproj.filters @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file