From 368cec646519bad70b936380d0741efee09abc60 Mon Sep 17 00:00:00 2001 From: Alan Snyder Date: Sat, 11 Nov 2023 20:02:10 -0800 Subject: [PATCH 1/3] Create ZIP with symlinks and changes to work on arm64 --- archive_jnf.sh | 7 +++++++ build.gradle.kts | 8 +++----- openjdk | 1 - 3 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 archive_jnf.sh delete mode 160000 openjdk diff --git a/archive_jnf.sh b/archive_jnf.sh new file mode 100644 index 0000000..ee8b8a0 --- /dev/null +++ b/archive_jnf.sh @@ -0,0 +1,7 @@ +#!/bin/sh -e + +echo "Running archive_jnf.sh" +pwd +mkdir frameworks +rm -f frameworks/JavaNativeFoundation.framework.zip +cd buildNative/Frameworks; zip --symlinks -r ../../frameworks/JavaNativeFoundation.framework.zip JavaNativeFoundation.framework diff --git a/build.gradle.kts b/build.gradle.kts index f5f3900..652d5f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,14 +37,12 @@ releaseParams { } val buildJNF by tasks.registering(Exec::class) { - commandLine("sh", "build_jnf.sh") + commandLine("arch", "-x86_64", "/bin/bash", "build_jnf.sh") } -val archiveJNF by tasks.registering(Zip::class) { +val archiveJNF by tasks.registering(Exec::class) { dependsOn(buildJNF) - archiveFileName.set("JavaNativeFoundation.framework.zip") - destinationDirectory.set(project.buildDir.resolve("frameworks")) - from("buildNative/Frameworks/JavaNativeFoundation.framework") + commandLine("sh", "archive_jnf.sh"); } fun registerJNFConfiguration(architecture : String) = configurations.registering { diff --git a/openjdk b/openjdk deleted file mode 160000 index 356491b..0000000 --- a/openjdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 356491bda24e6c4781c6b650f4efda05a6bc1296 From b5a3d1834f682defc77fbafb35e12ad614571d2e Mon Sep 17 00:00:00 2001 From: Alan Snyder Date: Sun, 12 Nov 2023 16:32:45 -0800 Subject: [PATCH 2/3] A better fix for building on arm --- build.gradle.kts | 2 +- build_jnf.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 652d5f3..c30d124 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,7 @@ releaseParams { } val buildJNF by tasks.registering(Exec::class) { - commandLine("arch", "-x86_64", "/bin/bash", "build_jnf.sh") + commandLine("sh", "build_jnf.sh") } val archiveJNF by tasks.registering(Exec::class) { diff --git a/build_jnf.sh b/build_jnf.sh index 1a4a4c6..9dd72c9 100644 --- a/build_jnf.sh +++ b/build_jnf.sh @@ -2,7 +2,7 @@ if [[ $(arch) == "arm64" ]] ; then echo "Re-execing build using Rosetta." >&2 - exec arch -x86_64 ${0} "${@}" + exec arch -x86_64 /bin/sh ${0} "${@}" fi SDK_NAME=macosx From b1a0f27ae04ba0db0c20fc6d2dd67c019553a077 Mon Sep 17 00:00:00 2001 From: Alan Snyder Date: Thu, 16 Nov 2023 12:42:32 -0800 Subject: [PATCH 3/3] Add script to build manually, update script to create ZIP with symlinks and also DMG --- Info.plist | Bin 0 -> 605 bytes archive_jnf.sh | 15 +- build_jnf2.sh | 75 +++ dsym-Info.plist | 20 + module.modulemap | 5 + src/JavaNativeFoundation/JNFAssert.h | 104 ++++ src/JavaNativeFoundation/JNFAssert.m | 69 +++ src/JavaNativeFoundation/JNFAutoreleasePool.h | 52 ++ src/JavaNativeFoundation/JNFAutoreleasePool.m | 102 ++++ src/JavaNativeFoundation/JNFDate.h | 65 +++ src/JavaNativeFoundation/JNFDate.m | 81 +++ src/JavaNativeFoundation/JNFException.h | 81 +++ src/JavaNativeFoundation/JNFException.m | 288 +++++++++++ src/JavaNativeFoundation/JNFJNI.h | 297 +++++++++++ src/JavaNativeFoundation/JNFJNI.m | 444 ++++++++++++++++ src/JavaNativeFoundation/JNFJObjectWrapper.h | 72 +++ src/JavaNativeFoundation/JNFJObjectWrapper.m | 137 +++++ src/JavaNativeFoundation/JNFNumber.h | 64 +++ src/JavaNativeFoundation/JNFNumber.m | 99 ++++ src/JavaNativeFoundation/JNFObject.h | 56 ++ src/JavaNativeFoundation/JNFObject.m | 65 +++ src/JavaNativeFoundation/JNFPath.h | 55 ++ src/JavaNativeFoundation/JNFPath.m | 58 +++ src/JavaNativeFoundation/JNFRunLoop.h | 53 ++ src/JavaNativeFoundation/JNFRunLoop.m | 78 +++ src/JavaNativeFoundation/JNFRunnable.h | 48 ++ src/JavaNativeFoundation/JNFRunnable.m | 84 +++ src/JavaNativeFoundation/JNFString.h | 71 +++ src/JavaNativeFoundation/JNFString.m | 109 ++++ src/JavaNativeFoundation/JNFThread.h | 80 +++ src/JavaNativeFoundation/JNFThread.m | 174 +++++++ src/JavaNativeFoundation/JNFTypeCoercion.h | 101 ++++ src/JavaNativeFoundation/JNFTypeCoercion.m | 480 ++++++++++++++++++ .../JavaNativeFoundation-Info.plist | 26 + .../JavaNativeFoundation.h | 49 ++ .../Modules/module.modulemap | 5 + src/JavaNativeFoundation/debug.h | 53 ++ src/JavaNativeFoundation/debug.m | 107 ++++ 38 files changed, 3817 insertions(+), 5 deletions(-) create mode 100644 Info.plist create mode 100644 build_jnf2.sh create mode 100644 dsym-Info.plist create mode 100644 module.modulemap create mode 100644 src/JavaNativeFoundation/JNFAssert.h create mode 100644 src/JavaNativeFoundation/JNFAssert.m create mode 100644 src/JavaNativeFoundation/JNFAutoreleasePool.h create mode 100644 src/JavaNativeFoundation/JNFAutoreleasePool.m create mode 100644 src/JavaNativeFoundation/JNFDate.h create mode 100644 src/JavaNativeFoundation/JNFDate.m create mode 100644 src/JavaNativeFoundation/JNFException.h create mode 100644 src/JavaNativeFoundation/JNFException.m create mode 100644 src/JavaNativeFoundation/JNFJNI.h create mode 100644 src/JavaNativeFoundation/JNFJNI.m create mode 100644 src/JavaNativeFoundation/JNFJObjectWrapper.h create mode 100644 src/JavaNativeFoundation/JNFJObjectWrapper.m create mode 100644 src/JavaNativeFoundation/JNFNumber.h create mode 100644 src/JavaNativeFoundation/JNFNumber.m create mode 100644 src/JavaNativeFoundation/JNFObject.h create mode 100644 src/JavaNativeFoundation/JNFObject.m create mode 100644 src/JavaNativeFoundation/JNFPath.h create mode 100644 src/JavaNativeFoundation/JNFPath.m create mode 100644 src/JavaNativeFoundation/JNFRunLoop.h create mode 100644 src/JavaNativeFoundation/JNFRunLoop.m create mode 100644 src/JavaNativeFoundation/JNFRunnable.h create mode 100644 src/JavaNativeFoundation/JNFRunnable.m create mode 100644 src/JavaNativeFoundation/JNFString.h create mode 100644 src/JavaNativeFoundation/JNFString.m create mode 100644 src/JavaNativeFoundation/JNFThread.h create mode 100644 src/JavaNativeFoundation/JNFThread.m create mode 100644 src/JavaNativeFoundation/JNFTypeCoercion.h create mode 100644 src/JavaNativeFoundation/JNFTypeCoercion.m create mode 100644 src/JavaNativeFoundation/JavaNativeFoundation-Info.plist create mode 100644 src/JavaNativeFoundation/JavaNativeFoundation.h create mode 100644 src/JavaNativeFoundation/Modules/module.modulemap create mode 100644 src/JavaNativeFoundation/debug.h create mode 100644 src/JavaNativeFoundation/debug.m diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..ff784fb0544ad661f6c95fdcf11bf3a04633e0a3 GIT binary patch literal 605 zcmZuu%}x|S5UySo@n>cD5zs^f58!0rKsZTYVI$(MWM=^nB$H;QcDBPzcc-Uk!jKqm z9`xdgBZ(*DGx!wTc>p|oNk&;-tl8k7UID?%%jNa~980MCrCDi+QMe}p5m9ZEdCfp8`NwdJ2t&<{_+~$S~<}2)whvz)TeyIsveCKzB z6ltFNWo9w04}DhAcd@`k>6C8J!+1R6a@T*8r(E`sN26(4N|#2&L;LSoLmFz*&9_F# zIB*u;@`87{6$Q2xy%Tw(8@~)y>T<0UbpJ>2H(al`>3yF&?(p!v|DnG{(r~KPQxnc7S-kO1%ALUxPjknimkHO?1&ZYl6_>K*k|^YePdVb R8phIKe=RIe?8G#he*hr{s__5- literal 0 HcmV?d00001 diff --git a/archive_jnf.sh b/archive_jnf.sh index ee8b8a0..ee187bb 100644 --- a/archive_jnf.sh +++ b/archive_jnf.sh @@ -1,7 +1,12 @@ #!/bin/sh -e -echo "Running archive_jnf.sh" -pwd -mkdir frameworks -rm -f frameworks/JavaNativeFoundation.framework.zip -cd buildNative/Frameworks; zip --symlinks -r ../../frameworks/JavaNativeFoundation.framework.zip JavaNativeFoundation.framework +distdir=$(pwd)/dist + +mkdir ${distdir} +rm -f ${distdir}/JavaNativeFoundation.zip +rm -f ${distdir}/JavaNativeFoundation.dmg + +(cd buildNative/Frameworks; zip --symlinks -r ${distdir}/JavaNativeFoundation.zip JavaNativeFoundation.framework) + +hdiutil create -srcfolder buildNative/Frameworks -volname JavaNativeFoundation ${distdir}/JavaNativeFoundation.dmg + diff --git a/build_jnf2.sh b/build_jnf2.sh new file mode 100644 index 0000000..175f36a --- /dev/null +++ b/build_jnf2.sh @@ -0,0 +1,75 @@ +#!/bin/sh -e + +SDK_NAME=macosx + +CONCURRENCY=$(sysctl -n hw.activecpu) +CODE_SIGN_IDENTITY=${AMFITRUSTED_IDENTITY:--} +DEBUG_LEVEL=release + +# Exporting this such that /usr/bin/clang uses it implictly +export SDKROOT=$(xcrun --sdk ${SDK_NAME} --show-sdk-path) +MACOSX_DEPLOYMENT_TARGET=10.10 +export LD_DYLIB_INSTALL_NAME=@rpath/JavaNativeFoundation.framework/Versions/A/JavaNativeFoundation + +cc=/usr/bin/cc +lipo=/usr/bin/lipo +srcdir=$(pwd)/src/JavaNativeFoundation +include1=${JAVA_HOME}/include +include2=${JAVA_HOME}/include/darwin +include3=${srcdir}/.. + +topdir=$(pwd) +libdir=$(pwd)/buildNative/lib +fwdir=$(pwd)/buildNative/Frameworks + +build1() { + arch=$1 + echo building for $arch + ${cc} -arch $arch -o ${libdir}/lib$arch.dylib -I${include1} -I${include2} -I${include3} -framework Cocoa \ + -dynamiclib -ObjC -fvisibility=hidden -install_name ${LD_DYLIB_INSTALL_NAME} -g \ + -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET} -Wl,-current_version,80 -Wl,-compatibility_version,1 \ + ${srcdir}/*.m +} + +do_jnf() { + rm -rf buildNative + mkdir -p buildNative/Frameworks + mkdir -p buildNative/lib + build1 x86_64 + build1 arm64 + ${lipo} ${libdir}/libx86_64.dylib ${libdir}/libarm64.dylib -create -output ${libdir}/JavaNativeFoundation + ${lipo} ${libdir}/libx86_64.dylib.dSYM/Contents/Resources/DWARF/libx86_64.dylib \ + ${libdir}/libarm64.dylib.dSYM/Contents/Resources/DWARF/libarm64.dylib \ + -create -output ${libdir}/JavaNativeFoundation-DSYM + + cd ${fwdir} + mkdir JavaNativeFoundation.framework + cd JavaNativeFoundation.framework + mkdir -p Versions/A/Headers + mkdir -p Versions/A/Resources + mkdir -p Versions/A/Modules + cp -p ${libdir}/JavaNativeFoundation Versions/A + cp -p ${srcdir}/J*.h Versions/A/Headers + cp -p ${srcdir}/Modules/module.modulemap Versions/A/Modules + xcrun tapi stubify --filetype=tbd-v5 -o Versions/A/JavaNativeFoundation.tbd \ + Versions/A/JavaNativeFoundation + cp -p ${topdir}/Info.plist Versions/A/Resources + + (cd Versions; ln -s A Current) + ln -s Versions/Current/Headers . + ln -s Versions/Current/Resources . + ln -s Versions/Current/Modules . + ln -s Versions/Current/JavaNativeFoundation . + ln -s Versions/Current/JavaNativeFoundation.tbd . + + cd ${fwdir} + mkdir -p JavaNativeFoundation.framework.dSYM/Contents/Resources/DWARF + cp -p ${libdir}/JavaNativeFoundation-DSYM JavaNativeFoundation.framework.dSYM/Contents/Resources/DWARF/JavaNativeFoundation + cp -p ${topdir}/dsym-Info.plist JavaNativeFoundation.framework.dSYM/Contents/Info.plist + + codesign --sign ${CODE_SIGN_IDENTITY} --timestamp --force --verbose JavaNativeFoundation.framework/*.tbd + codesign --sign ${CODE_SIGN_IDENTITY} --timestamp --force --verbose JavaNativeFoundation.framework + codesign --sign ${CODE_SIGN_IDENTITY} --timestamp --force --verbose JavaNativeFoundation.framework.dSYM +} + +do_jnf diff --git a/dsym-Info.plist b/dsym-Info.plist new file mode 100644 index 0000000..54e4ffa --- /dev/null +++ b/dsym-Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.com.apple.JavaNativeFoundation + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 80 + CFBundleVersion + 80 + + diff --git a/module.modulemap b/module.modulemap new file mode 100644 index 0000000..bde3b31 --- /dev/null +++ b/module.modulemap @@ -0,0 +1,5 @@ +framework module JavaNativeFoundation [extern_c] { + umbrella header "JavaNativeFoundation.h" + export * + module * { export * } +} diff --git a/src/JavaNativeFoundation/JNFAssert.h b/src/JavaNativeFoundation/JNFAssert.h new file mode 100644 index 0000000..9bfbfb2 --- /dev/null +++ b/src/JavaNativeFoundation/JNFAssert.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Assertions used by the JNF_COCOA_ENTER()/JNF_COCOA_EXIT() and class + * caching macros. When building debug builds, improper use of the caching + * macros will trigger warnings output to the console. + */ + +#import + +#ifdef DEBUG +#define JAVA_ASSERTIONS_ON +#endif /* DEBUG */ + +// Use the WARN macro to send a message to stderr in the +// debug build. It gets removed from the optimized build +// during preprocessing. +#ifdef DEBUG +#define JNF_WARN JNFDebugWarning +#else +#define JNF_WARN if (0) JNFDebugWarning +#endif /* DEBUG */ + +__BEGIN_DECLS + +JNF_EXPORT extern void JNFDebugWarning(const char *fmt, ...); + +JNF_EXPORT extern void JNFAssertionFailure(const char *file, int line, const char *condition, const char *msg); + +#ifdef JAVA_ASSERTIONS_ON + +#define JNF_ASSERT_FAILURE(condition, msg) \ + JNFAssertionFailure(__FILE__, __LINE__, condition, msg) \ + + +#define JNF_ASSERT_MSG(condition, msg) \ +do { \ + if (!(condition)) { \ + JNF_ASSERT_FAILURE(#condition, msg); \ + } \ +} while(0) \ + + +#define JNF_ASSERT_COND(condition) \ + JNF_ASSERT_MSG(condition, NULL) \ + + +#define JNF_EXCEPTION_WARN(env, msg) \ +do { \ + (*(env))->ExceptionDescribe(env); \ + JNF_ASSERT_FAILURE("Java exception thrown", msg); \ +} while (0) \ + + +#define JNF_ASSERT_NO_EXCEPTION_MSG(env, msg) \ +if ((*(env))->ExceptionOccurred(env)) { \ + JNF_EXCEPTION_WARN(env, msg); \ +} \ + + +#define JNF_ASSERT_NO_EXCEPTION(env) \ + JNF_ASSERT_NO_EXCEPTION_MSG(env, NULL) \ + +#else + +#define JNF_ASSERT_COND(condition) +#define JNF_ASSERT_MSG(condition, msg) +#define JNF_EXCEPTION_WARN(env, msg) +#define JNF_ASSERT_NO_EXCEPTION(env) +#define JNF_ASSERT_NO_EXCEPTION_MSG(env, msg) + +#endif /* JAVA_ASSERTIONS_ON */ + +JNF_EXPORT extern void JNFDumpJavaStack(JNIEnv *env); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFAssert.m b/src/JavaNativeFoundation/JNFAssert.m new file mode 100644 index 0000000..fceb8f8 --- /dev/null +++ b/src/JavaNativeFoundation/JNFAssert.m @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFJNI.h" +#import "JNFAssert.h" + +#import "debug.h" + +static void JNFDebugMessageV(const char *fmt, va_list args) { + // Prints a message and breaks into debugger. + fprintf(stderr, "JavaNativeFoundation: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +static void JNFDebugMessage(const char *fmt, ...) { + // Takes printf args and then calls DebugBreak + va_list args; + va_start(args, fmt); + JNFDebugMessageV(fmt, args); + va_end(args); +} + +void JNFDebugWarning(const char *fmt, ...) { + // Takes printf args and then calls DebugBreak + va_list args; + va_start(args, fmt); + JNFDebugMessageV(fmt, args); + va_end(args); +} + +void JNFAssertionFailure(const char *file, int line, const char *condition, const char *msg) { + JNFDebugMessage("Assertion failure: %s", condition); + if (msg) JNFDebugMessage(msg); + JNFDebugMessage("File %s; Line %d", file, line); +} + +void JNFDumpJavaStack(JNIEnv *env) { + static JNF_CLASS_CACHE(jc_Thread, "java/lang/Thread"); + static JNF_STATIC_MEMBER_CACHE(jsm_Thread_dumpStack, jc_Thread, "dumpStack", "()V"); + JNFCallVoidMethod(env, jc_Thread.cls, jsm_Thread_dumpStack); +} diff --git a/src/JavaNativeFoundation/JNFAutoreleasePool.h b/src/JavaNativeFoundation/JNFAutoreleasePool.h new file mode 100644 index 0000000..7d3c543 --- /dev/null +++ b/src/JavaNativeFoundation/JNFAutoreleasePool.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Utility class used by the JNF_COCOA_ENTER()/JNF_COCOA_EXIT() macros + * from JNFJNI.h. Do not use this class or releated functions directly. + */ + +#import + +#import + +__BEGIN_DECLS + +typedef void JNFAutoreleasePoolToken; + +// JNFNativeMethodEnter - called on entry to each native method by the +// JNF_COCOA_ENTER(env) macro in JNFJNI.h +JNF_EXPORT extern JNFAutoreleasePoolToken *JNFNativeMethodEnter(void); + +// JNFNativeMethodExit - called on exit from each native method by the +// JNF_COCOA_EXIT(env) macro in JNFJNI.h +JNF_EXPORT extern void JNFNativeMethodExit(JNFAutoreleasePoolToken *token); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFAutoreleasePool.m b/src/JavaNativeFoundation/JNFAutoreleasePool.m new file mode 100644 index 0000000..50d513b --- /dev/null +++ b/src/JavaNativeFoundation/JNFAutoreleasePool.m @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * The JNFAutoreleasePool manages setting up and tearing down autorelease + * pools for Java calls into the Cocoa frameworks. + * + * The external entry point into this machinery is JNFMethodEnter() and JNFMethodExit(). + */ + +#import "JNFAutoreleasePool.h" + +#import + +// These are vended by the Objective-C runtime, but they are unfortunately +// not available as API in the macOS SDK. We are following suit with swift +// and clang in declaring them inline here. They canot be removed or changed +// in the OS without major bincompat ramifications. +// +// These were added in macOS 10.7. +void * _Nonnull objc_autoreleasePoolPush(void); +void objc_autoreleasePoolPop(void * _Nonnull context); + +#if TIMED +static int64_t elapsedTime = 0; +#endif + +#pragma mark - +#pragma mark External API + +// JNFNativeMethodEnter - called on entry to each native method +// +// It sets up an autorelease pool, and will return a token if +// JNFNativeMethodExit should be called. It attempts to consider +// how much time has elapsed since the last autorelease pop. + +JNFAutoreleasePoolToken *JNFNativeMethodEnter() { +#if TIMED + int64_t start = mach_absolute_time(); +#endif + + JNFAutoreleasePoolToken * const tokenToReturn = objc_autoreleasePoolPush(); + +#if TIMED + elapsedTime += (mach_absolute_time() - start); +#endif + + return tokenToReturn; +} + + +// JNFNativeMethodExit - called on exit from native methods +// +// This method is only called on exit from the first +// native method to appear in the execution stack. +// This function does not need to be called on exit +// from the inner native methods (as an optimization). +// JNFNativeMethodEnter sets the token to non-nil if +// JNFNativeMethodExit needs to be called on exit. + +void JNFNativeMethodExit(JNFAutoreleasePoolToken *token) { + +#if TIMED + int64_t start = mach_absolute_time(); +#endif + + objc_autoreleasePoolPop(token); + +#if TIMED + elapsedTime += (mach_absolute_time() - start); + + NSLog(@"elapsedTime: %llu", elapsedTime); + // elapsedTime = 0; +#endif +} diff --git a/src/JavaNativeFoundation/JNFDate.h b/src/JavaNativeFoundation/JNFDate.h new file mode 100644 index 0000000..93f0e6d --- /dev/null +++ b/src/JavaNativeFoundation/JNFDate.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Functions to convert between date container classes. + */ + +#import + +#import + +__BEGIN_DECLS + +/* + * Converts java.util.Calendar and java.util.Date to an NSDate + * NOTE: Return value is auto-released. + */ +JNF_EXPORT extern NSDate *JNFJavaToNSDate(JNIEnv *env, jobject date); + +/* + * Converts an NSDate to a java.util.Calendar + * NOTE: This returns a JNI local ref. Any code that calls this should call DeleteLocalRef when done with the return value. + */ +JNF_EXPORT extern jobject JNFNSToJavaCalendar(JNIEnv *env, NSDate *date); + +/* + * Converts a millisecond time interval since the Java Jan 1, 1970 epoch into an + * NSTimeInterval since Mac OS X's Jan 1, 2001 epoch. + */ +JNF_EXPORT extern NSTimeInterval JNFJavaMillisToNSTimeInterval(jlong javaMillisSince1970); + +/* + * Converts an NSTimeInterval since the Mac OS X Jan 1, 2001 epoch into a + * Java millisecond time interval since Java's Jan 1, 1970 epoch. + */ +JNF_EXPORT extern jlong JNFNSTimeIntervalToJavaMillis(NSTimeInterval intervalSince2001); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFDate.m b/src/JavaNativeFoundation/JNFDate.m new file mode 100644 index 0000000..f3b4042 --- /dev/null +++ b/src/JavaNativeFoundation/JNFDate.m @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFDate.h" +#import "JNFJNI.h" + + +static JNF_CLASS_CACHE(sjc_Calendar, "java/util/Calendar"); +static JNF_CLASS_CACHE(sjc_Date, "java/util/Date"); + +JNF_EXPORT extern NSTimeInterval JNFJavaMillisToNSTimeInterval(jlong javaMillisSince1970) +{ + return (NSTimeInterval)(((double)javaMillisSince1970 / 1000.0) - NSTimeIntervalSince1970); +} + +JNF_EXPORT extern jlong JNFNSTimeIntervalToJavaMillis(NSTimeInterval intervalSince2001) +{ + return (jlong)((intervalSince2001 + NSTimeIntervalSince1970) * 1000.0); +} + +JNF_EXPORT extern NSDate *JNFJavaToNSDate(JNIEnv *env, jobject date) +{ + if (date == NULL) return nil; + + jlong millis = 0; + if (JNFIsInstanceOf(env, date, &sjc_Calendar)) { + static JNF_MEMBER_CACHE(jm_getTimeInMillis, sjc_Calendar, "getTimeInMillis", "()J"); + millis = JNFCallLongMethod(env, date, jm_getTimeInMillis); + } else if (JNFIsInstanceOf(env, date, &sjc_Date)) { + static JNF_MEMBER_CACHE(jm_getTime, sjc_Date, "getTime", "()J"); + millis = JNFCallLongMethod(env, date, jm_getTime); + } + + if (millis == 0) { + return nil; + } + + return [NSDate dateWithTimeIntervalSince1970:((double)millis / 1000.0)]; +} + +JNF_EXPORT extern jobject JNFNSToJavaCalendar(JNIEnv *env, NSDate *date) +{ + if (date == nil) return NULL; + + const jlong millis = (jlong)([date timeIntervalSince1970] * 1000.0); + + static JNF_STATIC_MEMBER_CACHE(jsm_getInstance, sjc_Calendar, "getInstance", "()Ljava/util/Calendar;"); + jobject calendar = JNFCallStaticObjectMethod(env, jsm_getInstance); + + static JNF_MEMBER_CACHE(jm_setTimeInMillis, sjc_Calendar, "setTimeInMillis", "(J)V"); + JNFCallVoidMethod(env, calendar, jm_setTimeInMillis, millis); + + return calendar; +} diff --git a/src/JavaNativeFoundation/JNFException.h b/src/JavaNativeFoundation/JNFException.h new file mode 100644 index 0000000..c93be64 --- /dev/null +++ b/src/JavaNativeFoundation/JNFException.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * JNFExceptions handle bridging exceptions between Foundation and Java. NSExceptions are + * caught by the JNF_COCOA_ENTER()/JNF_COCOA_EXIT() macros, and transformed and thrown as + * Java exceptions at the JNI boundry. The macros in JNFJNI.h also check for Java exceptions + * and rethrow them as NSExceptions until they hit an @try/@catch block, or the + * JNF_COCOA_ENTER()/JNF_COCOA_EXIT() macros. + */ + +#import + +#import + +__BEGIN_DECLS + +// Some exception class names. +// These strings contain the full class name of each Java exception, so +// they are handy to use when you need to throw an exception. +JNF_EXPORT extern const char *kOutOfMemoryError; +JNF_EXPORT extern const char *kClassNotFoundException; +JNF_EXPORT extern const char *kNullPointerException; +JNF_EXPORT extern const char *kIllegalAccessException; +JNF_EXPORT extern const char *kIllegalArgumentException; +JNF_EXPORT extern const char *kNoSuchFieldException; +JNF_EXPORT extern const char *kNoSuchMethodException; +JNF_EXPORT extern const char *kRuntimeException; + +// JNFException - a subclass of NSException that wraps a Java exception +// +// When a java exception is thrown out to a native method, use +raiseUnnamedException: +// to turn it into an NSException. When returning out of a native method in +// which an NSException has been raised, use the -raiseToJava: method to turn +// it back into a Java exception and "throw" it, in the Java sense. + +JNF_EXPORT +@interface JNFException : NSException + ++ (void)raiseUnnamedException:(JNIEnv *)env; ++ (void)raise:(JNIEnv *)env throwable:(jthrowable)throwable; ++ (void)raise:(JNIEnv *)env as:(const char *)javaExceptionType reason:(const char *)reasonMsg; + +- init:(JNIEnv *)env throwable:(jthrowable)throwable; +- init:(JNIEnv *)env as:(const char *)javaExceptionType reason:(const char *)reasonMsg; + ++ (void)throwToJava:(JNIEnv *)env exception:(NSException *)exception; ++ (void)throwToJava:(JNIEnv *)env exception:(NSException *)exception as:(const char *)javaExceptionType; + +- (void)raiseToJava:(JNIEnv *)env; + +@end + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFException.m b/src/JavaNativeFoundation/JNFException.m new file mode 100644 index 0000000..28a75dd --- /dev/null +++ b/src/JavaNativeFoundation/JNFException.m @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFException.h" + +#import +#import + +#import "JNFObject.h" +#import "JNFString.h" +#import "JNFAssert.h" +#import "JNFThread.h" +#import "debug.h" + + +#define JAVA_LANG "java/lang/" +const char* kOutOfMemoryError = JAVA_LANG "OutOfMemoryError"; +const char* kClassNotFoundException = JAVA_LANG "ClassNotFoundException"; +const char* kNullPointerException = JAVA_LANG "NullPointerException"; +const char* kIllegalAccessException = JAVA_LANG "IllegalAccessException"; +const char* kIllegalArgumentException = JAVA_LANG "IllegalArgumentException"; +const char* kNoSuchFieldException = JAVA_LANG "NoSuchFieldException"; +const char* kNoSuchMethodException = JAVA_LANG "NoSuchMethodException"; +const char* kRuntimeException = JAVA_LANG "RuntimeException"; + +@interface JNFException () +@property (readwrite, nonatomic, assign) jthrowable javaException; +@end + +@interface JNFException(_JNFPrivateExceptionLifecycle) + +- (void) _setThrowable:(jthrowable)throwable withEnv:(JNIEnv *)env; +- (void) _clearThrowableWithEnv:(JNIEnv *)env; +- (void) _setThrowable:(jthrowable)throwable withNonNullEnv:(JNIEnv *)env; +- (void) _clearThrowableWithNonNullEnv:(JNIEnv *)env; + +@end + + +@implementation JNFException + +- initUnnamed:(JNIEnv *)env { + JNF_ASSERT_COND(env); + jthrowable throwable = (*env)->ExceptionOccurred(env); + if (throwable) return [self init:env throwable:throwable]; + + return [self initWithName:@"JavaNativeException" reason:@"See Java exception" userInfo:nil]; +} + ++ (void)raiseUnnamedException:(JNIEnv *)env { + JNF_ASSERT_COND(env); + [[[[JNFException alloc] initUnnamed:env] autorelease] raise]; +} + ++ (void)raise:(JNIEnv *)env throwable:(jthrowable)throwable { + JNF_ASSERT_COND(env); + [[[[JNFException alloc] init:env throwable:throwable] autorelease] raise]; +} + ++ (void)raise:(JNIEnv *)env as:(const char *)javaExceptionType reason:(const char *)reasonMsg { + JNF_ASSERT_COND(env); + [[[[JNFException alloc] init:env as:javaExceptionType reason:reasonMsg] autorelease] raise]; +} + +- init:(JNIEnv *)env throwable:(jthrowable)throwable { + [self _setThrowable:throwable withEnv:env]; + (*env)->ExceptionClear(env); // The exception will be rethrown in -raiseToJava + + static jclass jc_Throwable = NULL; + if (jc_Throwable == NULL) { + jc_Throwable = (*env)->FindClass(env, "java/lang/Throwable"); + jthrowable unexpected = (*env)->ExceptionOccurred(env); + if (unexpected) { + (*env)->ExceptionClear(env); + return [self initWithName:@"JavaNativeException" reason:@"Internal JNF Error: could not find Throwable class" userInfo:nil]; + } + } + + static jmethodID jm_Throwable_getMessage = NULL; + if (jm_Throwable_getMessage == NULL && jc_Throwable != NULL) { + jm_Throwable_getMessage = (*env)->GetMethodID(env, jc_Throwable, "toString", "()Ljava/lang/String;"); + jthrowable unexpected = (*env)->ExceptionOccurred(env); + if (unexpected) { + (*env)->ExceptionClear(env); + return [self initWithName:@"JavaNativeException" reason:@"Internal JNF Error: could not find Throwable.toString() method" userInfo:nil]; + } + } + + if (jm_Throwable_getMessage == NULL) { + return [self initWithName:@"JavaNativeException" reason:@"Internal JNF Error: exception occurred, unable to determine cause" userInfo:nil]; + } + + jobject msg = (*env)->CallObjectMethod(env, throwable, jm_Throwable_getMessage); + jthrowable unexpected = (*env)->ExceptionOccurred(env); + if (unexpected) { + (*env)->ExceptionClear(env); + return [self initWithName:@"JavaNativeException" reason:@"Internal JNF Error: failed calling Throwable.toString()" userInfo:nil]; + } + + NSString *reason = JNFJavaToNSString(env, msg); + (*env)->DeleteLocalRef(env, msg); + return [self initWithName:@"JavaNativeException" reason:reason userInfo:nil]; +} + +- init:(JNIEnv *)env as:(const char *)javaExceptionType reason:(const char *)reasonMsg { + jclass exceptionClass = NULL; + char *buf = NULL; + + JNF_ASSERT_COND(env); + if (javaExceptionType != NULL) { + exceptionClass = (*env)->FindClass(env, javaExceptionType); + } + + if (exceptionClass == NULL) { + // Try to throw an AWTError exception + static jthrowable panicExceptionClass = NULL; + const char* panicExceptionName = kRuntimeException; + if (panicExceptionClass == NULL) { + jclass cls = (*env)->FindClass(env, panicExceptionName); + if (cls != NULL) { + panicExceptionClass = (*env)->NewGlobalRef(env, cls); + } + } + + exceptionClass = panicExceptionClass; + if (javaExceptionType == NULL) { + reasonMsg = "Missing Java exception class name while trying to throw a new Java exception"; + } else { + // Quick and dirty thread-safe message buffer. + buf = calloc(1, 512); + if (buf != NULL) { + sprintf(buf, "Unknown throwable class: %s.80", javaExceptionType); + reasonMsg = buf; + } else { + reasonMsg = "Unknown throwable class, out of memory!"; + } + } + javaExceptionType = panicExceptionName; + } + + // Can't throw squat if there's no class to throw + if (exceptionClass != NULL) { + (*env)->ThrowNew(env, exceptionClass, reasonMsg); + jthrowable ex = (*env)->ExceptionOccurred(env); + if (ex) { + (*env)->ExceptionClear(env); // Exception will be rethrown in -raiseToJava + } + [self _setThrowable:ex withEnv:env]; + } + + if (reasonMsg == NULL) reasonMsg = "unknown"; + + @try { + return [self initWithName:[NSString stringWithUTF8String:javaExceptionType] + reason:[NSString stringWithUTF8String:reasonMsg] + userInfo:nil]; + } @finally { + if (buf != NULL) free(buf); + } + + return self; +} + ++ (void)throwToJava:(JNIEnv *)env exception:(NSException *)exception { + [self throwToJava:env exception:exception as:kRuntimeException]; +} + ++ (void)throwToJava:(JNIEnv *)env exception:(NSException *)exception as:(const char *)javaExceptionType{ + if (![exception isKindOfClass:[JNFException class]]) { + exception = [[JNFException alloc] init:env as:javaExceptionType reason:[[NSString stringWithFormat:@"Non-Java exception raised, not handled! (Original problem: %@)", [exception reason]] UTF8String]]; + [exception autorelease]; + JNF_WARN("NSException not handled by native method. Passing to Java."); + } + + [(JNFException *)exception raiseToJava:env]; +} + +- (void)raiseToJava:(JNIEnv *)env { + jthrowable const javaException = self.javaException; + + JNF_ASSERT_COND(env); + JNF_ASSERT_COND(javaException != NULL); + (*env)->Throw(env, javaException); +} + +- (NSString *)description { + jthrowable const javaException = self.javaException; + NSString *desc = [super description]; + if (!javaException) return desc; + + @try { + JNFThreadContext ctx = JNFThreadDetachImmediately; + JNIEnv *env = JNFObtainEnv(&ctx); + if (!env) { + NSLog(@"JavaNativeFoundation: NULL JNIEnv error occurred obtaining Java exception description"); + return desc; + } + (*env)->ExceptionClear(env); + NSString *stackTrace = JNFGetStackTraceAsNSString(env, javaException); + JNFReleaseEnv(env, &ctx); + + return stackTrace; + } @catch (NSException *e) { + // we clearly blew up trying to print our own exception, so we should + // not try to do that again, even if it looks helpful - its a trap! + NSLog(@"JavaNativeFoundation error occurred obtaining Java exception description"); + } + return desc; +} + +- (void) _setThrowable:(jthrowable)throwable withEnv:(JNIEnv *)env { + if (env) { + [self _clearThrowableWithNonNullEnv:env]; + [self _setThrowable:throwable withNonNullEnv:env]; + return; + } + + JNFThreadContext threadContext = JNFThreadDetachImmediately; + env = JNFObtainEnv(&threadContext); + if (env == NULL) return; + + [self _clearThrowableWithNonNullEnv:env]; + [self _setThrowable:throwable withEnv:env]; + + JNFReleaseEnv(env, &threadContext); +} + +- (void) _clearThrowableWithEnv:(JNIEnv *)env { + if (env) { + [self _clearThrowableWithNonNullEnv:env]; + return; + } + + JNFThreadContext threadContext = JNFThreadDetachImmediately; + env = JNFObtainEnv(&threadContext); + if (env == NULL) return; // leak? + + [self _clearThrowableWithNonNullEnv:env]; + + JNFReleaseEnv(env, &threadContext); +} + +- (void) _setThrowable:(jthrowable)throwable withNonNullEnv:(JNIEnv *)env { + if (!throwable) return; + self.javaException = (*env)->NewGlobalRef(env, throwable); +} + +// delete and clear +- (void) _clearThrowableWithNonNullEnv:(JNIEnv *)env { + jthrowable const javaException = self.javaException; + if (!javaException) return; + self.javaException = NULL; + (*env)->DeleteGlobalRef(env, javaException); +} + +- (void) dealloc { + [self _clearThrowableWithEnv:NULL]; + [super dealloc]; +} + +@end diff --git a/src/JavaNativeFoundation/JNFJNI.h b/src/JavaNativeFoundation/JNFJNI.h new file mode 100644 index 0000000..a5b34fb --- /dev/null +++ b/src/JavaNativeFoundation/JNFJNI.h @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * The basic building blocks of writing Java JNI code that interacts with Objective-C. + * + * All JNI functions should call JNF_COCOA_ENTER()/JNF_COCOA_EXIT() to properly + * catch thrown NSExceptions and periodically flush the autorelease pool for the + * current thread. JNF_COCOA_DURING()/JNF_COCOA_HANDLE() should only be used when + * AppKit is known to not be initialized yet. + * + * JNF_CLASS_CACHE()/JNF_MEMBER_CACHE()/JNF_STATIC_MEMBER_CACHE()/JNF_CTOR_CACHE() + * all cache references to Java classes, methods, and variables for use by the + * GET/SET/CALL functions. These functions check for Java exceptions, immediately + * re-throwing them as JNFExceptions, and are simpler than their pure JNI equivalents. + */ + +#import +#import +#import + +#define JNF_EXPORT __attribute__ ((visibility ("default"))) API_UNAVAILABLE(ios) + +#import +#import + +__BEGIN_DECLS + +// from jlong.h +// All pointers in and out of JNI functions should be expressed as jlongs +// to accomodate for both 32-bit and 64-bit pointer sizes +#ifndef jlong_to_ptr +#define jlong_to_ptr(a) ((void *)(uintptr_t)(a)) +#endif + +#ifndef ptr_to_jlong +#define ptr_to_jlong(a) ((jlong)(uintptr_t)(a)) +#endif + +// JNF_COCOA_DURING - Outermost exception scope for a JNI native method +// +// Use this macro only if you don't want any autorelease pool set or +// other JNFThreadContext setup (ie, if the AppKit isn't running +// yet). Usually, you want to use JNF_COCOA_ENTER & JNF_COCOA_EXIT +#define JNF_COCOA_DURING(env) \ +@try { + + +// JNF_COCOA_HANDLE - Close of JNF_COCOA_DURING +// +// Use this macro to match an JNF_COCOA_DURING +// This macro ensures that no NSException is thrown into +// the VM. It turns NSExceptions into Java exceptions. +#define JNF_COCOA_HANDLE(env) \ +} @catch(NSException *localException) { \ + [JNFException throwToJava:env exception:localException]; \ +} \ + + +// JNF_COCOA_ENTER - Place at the beginning of every JNI method +// +// Sets up an exception handler and an autorelease pool if one is +// not already setup. +// +// Note: if the native method executes before AppKit is +// initialized, use JNF_COCOA_DURING. +#define JNF_COCOA_ENTER(env) \ +{ \ + JNFAutoreleasePoolToken* _token = JNFNativeMethodEnter(); \ + JNF_COCOA_DURING(env) + + +// JNF_COCOA_EXIT - Place at the end of every JNI method +// +// Catches NSExceptions and re-throws them as Java exceptions. +// Use this macro to match JNF_COCOA_ENTER. +#define JNF_COCOA_EXIT(env) \ + JNF_COCOA_HANDLE(env) \ + @finally { \ + if (_token) JNFNativeMethodExit(_token); \ + } \ +} + +// JNF_CHECK_AND_RETHROW_EXCEPTION - rethrows exceptions from Java +// +// Takes an exception thrown from Java, and transforms it into an +// NSException. The NSException should bubble up to the upper-most +// JNF_COCOA_ENTER/JNF_COCOA_EXIT pair, and then be re-thrown as +// a Java exception when returning from JNI. This check should be +// done after raw JNI operations which could cause a Java exception +// to be be thrown. The JNF{Get/Set/Call} macros below do this +// check automatically. +#define JNF_CHECK_AND_RETHROW_EXCEPTION(env) \ +{ \ + jthrowable _exception = (*env)->ExceptionOccurred(env); \ + if (_exception) [JNFException raise:env throwable:_exception]; \ +} + + +// Use JNF_CLASS_CACHE, JNF_MEMBER_CACHE, JNF_STATIC_MEMBER_CACHE +// and JNF_CTOR_CACHE as convenient ways to create +// JNFClassInfo and JNFMemberInfo records that can +// be passed to the utility functions that follow. + +// JNF_CLASS_CACHE - Create a JNFClassInfo struct +// +// Use this macro to define a JNFClassInfo struct. +// For example: +// JNF_CLASS_CACHE(jc_java_awt_Font, "java/awt/Font"); +// defines the symbol jc_java_awt_Font to point to the +// appropriately initialized JNFClassInfo struct. +// The "jc_" prefix is short for "java class." +#define JNF_CLASS_CACHE(cache_symbol, name) \ + JNFClassInfo cache_symbol = {name, NULL} + +// JNF_MEMBER_CACHE - Create a JNFMemberInfo struct +// +// This macro creates and initializes a JNFMemberInfo +// struct, and defines a pointer to it. Example: +// JNF_MEMBER_CACHE(jm_Font_isBold, jc_java_awt_Font, "isBold", "Z"); +// This defines the symbol jm_Font_isBold to point to a +// JNFMemberInfo struct that represents the isBold method +// of the class java.awt.Font. Use this macro for both +// fields and methods. +#define JNF_MEMBER_CACHE(cache_symbol, class_cache_symbol, name, sig) \ + JNFMemberInfo _ ## cache_symbol = {name, sig, NO, &class_cache_symbol, {NULL}}, *cache_symbol=&_ ## cache_symbol + +// JNF_STATIC_MEMBER_CACHE - Create a JNFMemberInfo struct for static members +// +// Same as JNF_MEMBER_CACHE, but used for static fields and mehods. +#define JNF_STATIC_MEMBER_CACHE(cache_symbol, class_cache_symbol, name, sig) \ + JNFMemberInfo _ ## cache_symbol = {name, sig, YES, &class_cache_symbol, {NULL}}, *cache_symbol=&_ ## cache_symbol + +// JNF_CTOR_CACHE - Create a JNFMemberInfo struct for a constructor +// +// Same as JNF_MEMBER_CACHE, but for constructors +#define JNF_CTOR_CACHE(cache_symbol, class_cache_symbol, sig) \ + JNFMemberInfo _ ## cache_symbol = {"", sig, NO, &class_cache_symbol, {NULL}}, *cache_symbol=&_ ## cache_symbol + + +// JNFClassInfo - struct for caching a java class reference +// +// Create one of these by using the JNF_CLASS_CACHE macro (below). +// The class ref is resolved lazily. +typedef struct _JNFClassInfo { + const char *name; // fully/qualified/ClassName + jclass cls; // The JNI global class reference. +} JNFClassInfo; + +// JNFMemberInfo - struct for caching a field or method ID +// +// Create these by using the JNF_MEMBER_CACHE macro (below). +// The member ID is resolved lazily. +typedef struct _JNFMemberInfo { + const char *name; // The name of the member + const char *sig; // The signature of the member + BOOL isStatic; // Is this member declared static? + JNFClassInfo *classInfo; // points to the JNFClassInfo struct of + // which this field/method is a member. + union _j { + jfieldID fieldID; // If field, the JNI field ID + jmethodID methodID; // If method, the JNI method ID + } j; +} JNFMemberInfo; + + +/* + * JNI Utility Functions + * + * These functions make use of class and method ID caching, so they + * are more efficient than simply calling their JNI equivalents directly. + * They also detect Java exceptions and throw a corresponding + * NSException when JNI returns with a Java exception. + * Therefore, you should be prepared to handle exceptions + * before they propagate either back to the VM or up + * to the run loop. + */ + +// JNFIsInstanceOf - returns whether obj is an instance of clazz +JNF_EXPORT extern BOOL JNFIsInstanceOf(JNIEnv *env, jobject obj, JNFClassInfo *clazz); + +// Creating instances +JNF_EXPORT extern jobject JNFNewObject(JNIEnv *env, JNFMemberInfo *constructor, ...); + +// Creating arrays +JNF_EXPORT extern jobjectArray JNFNewObjectArray (JNIEnv *env, JNFClassInfo *clazz, jsize length); +JNF_EXPORT extern jbooleanArray JNFNewBooleanArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jbyteArray JNFNewByteArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jcharArray JNFNewCharArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jshortArray JNFNewShortArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jintArray JNFNewIntArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jlongArray JNFNewLongArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jfloatArray JNFNewFloatArray (JNIEnv *env, jsize length); +JNF_EXPORT extern jdoubleArray JNFNewDoubleArray (JNIEnv *env, jsize length); + +// Non-static getters +JNF_EXPORT extern jobject JNFGetObjectField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jboolean JNFGetBooleanField(JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jbyte JNFGetByteField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jchar JNFGetCharField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jshort JNFGetShortField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jint JNFGetIntField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jlong JNFGetLongField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jfloat JNFGetFloatField (JNIEnv *env, jobject obj, JNFMemberInfo *field); +JNF_EXPORT extern jdouble JNFGetDoubleField (JNIEnv *env, jobject obj, JNFMemberInfo *field); + +// Static getters +JNF_EXPORT extern jobject JNFGetStaticObjectField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jboolean JNFGetStaticBooleanField(JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jbyte JNFGetStaticByteField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jchar JNFGetStaticCharField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jshort JNFGetStaticShortField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jint JNFGetStaticIntField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jlong JNFGetStaticLongField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jfloat JNFGetStaticFloatField (JNIEnv *env, JNFMemberInfo *field); +JNF_EXPORT extern jdouble JNFGetStaticDoubleField (JNIEnv *env, JNFMemberInfo *field); + +// Non-static setters +JNF_EXPORT extern void JNFSetObjectField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jobject val); +JNF_EXPORT extern void JNFSetBooleanField(JNIEnv *env, jobject obj, JNFMemberInfo *field, jboolean val); +JNF_EXPORT extern void JNFSetByteField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jbyte val); +JNF_EXPORT extern void JNFSetCharField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jchar val); +JNF_EXPORT extern void JNFSetShortField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jshort val); +JNF_EXPORT extern void JNFSetIntField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jint val); +JNF_EXPORT extern void JNFSetLongField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jlong val); +JNF_EXPORT extern void JNFSetFloatField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jfloat val); +JNF_EXPORT extern void JNFSetDoubleField (JNIEnv *env, jobject obj, JNFMemberInfo *field, jdouble val); + +// Static setters +JNF_EXPORT extern void JNFSetStaticObjectField (JNIEnv *env, JNFMemberInfo *field, jobject val); +JNF_EXPORT extern void JNFSetStaticBooleanField(JNIEnv *env, JNFMemberInfo *field, jboolean val); +JNF_EXPORT extern void JNFSetStaticByteField (JNIEnv *env, JNFMemberInfo *field, jbyte val); +JNF_EXPORT extern void JNFSetStaticCharField (JNIEnv *env, JNFMemberInfo *field, jchar val); +JNF_EXPORT extern void JNFSetStaticShortField (JNIEnv *env, JNFMemberInfo *field, jshort val); +JNF_EXPORT extern void JNFSetStaticIntField (JNIEnv *env, JNFMemberInfo *field, jint val); +JNF_EXPORT extern void JNFSetStaticLongField (JNIEnv *env, JNFMemberInfo *field, jlong val); +JNF_EXPORT extern void JNFSetStaticFloatField (JNIEnv *env, JNFMemberInfo *field, jfloat val); +JNF_EXPORT extern void JNFSetStaticDoubleField (JNIEnv *env, JNFMemberInfo *field, jdouble val); + +// Calling instance methods +JNF_EXPORT extern void JNFCallVoidMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jobject JNFCallObjectMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jboolean JNFCallBooleanMethod(JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jbyte JNFCallByteMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jchar JNFCallCharMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jshort JNFCallShortMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jint JNFCallIntMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jlong JNFCallLongMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jfloat JNFCallFloatMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); +JNF_EXPORT extern jdouble JNFCallDoubleMethod (JNIEnv *env, jobject obj, JNFMemberInfo *method, ...); + +// Calling static methods +JNF_EXPORT extern void JNFCallStaticVoidMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jobject JNFCallStaticObjectMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jboolean JNFCallStaticBooleanMethod(JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jbyte JNFCallStaticByteMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jchar JNFCallStaticCharMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jshort JNFCallStaticShortMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jint JNFCallStaticIntMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jlong JNFCallStaticLongMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jfloat JNFCallStaticFloatMethod (JNIEnv *env, JNFMemberInfo *method, ...); +JNF_EXPORT extern jdouble JNFCallStaticDoubleMethod (JNIEnv *env, JNFMemberInfo *method, ...); + +// Global references +JNF_EXPORT extern jobject JNFNewGlobalRef(JNIEnv *env, jobject obj); +JNF_EXPORT extern void JNFDeleteGlobalRef(JNIEnv *env, jobject globalRef); +JNF_EXPORT extern jobject JNFNewWeakGlobalRef(JNIEnv *env, jobject obj); +JNF_EXPORT extern void JNFDeleteWeakGlobalRef(JNIEnv *env, jobject globalRef); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFJNI.m b/src/JavaNativeFoundation/JNFJNI.m new file mode 100644 index 0000000..1d20327 --- /dev/null +++ b/src/JavaNativeFoundation/JNFJNI.m @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFJNI.h" + +#import "JNFAssert.h" +#import "debug.h" + +// constants ripped from jvm.h +#define JVM_SIGNATURE_ARRAY '[' +#define JVM_SIGNATURE_BYTE 'B' +#define JVM_SIGNATURE_CHAR 'C' +#define JVM_SIGNATURE_CLASS 'L' +#define JVM_SIGNATURE_ENDCLASS ';' +#define JVM_SIGNATURE_ENUM 'E' +#define JVM_SIGNATURE_FLOAT 'F' +#define JVM_SIGNATURE_DOUBLE 'D' +#define JVM_SIGNATURE_FUNC '(' +#define JVM_SIGNATURE_ENDFUNC ')' +#define JVM_SIGNATURE_INT 'I' +#define JVM_SIGNATURE_LONG 'J' +#define JVM_SIGNATURE_SHORT 'S' +#define JVM_SIGNATURE_VOID 'V' +#define JVM_SIGNATURE_BOOLEAN 'Z' + +// IS_METHOD - Does the parameter point to a method member? +// +// This is used mostly in asserts. +#define IS_METHOD(member) (*(member->sig) == SIGNATURE_FUNC) + +static inline BOOL isMethod(JNFMemberInfo *member) { + return (*(member->sig)) == JVM_SIGNATURE_FUNC; +} + +static void JNFLookupClass(JNIEnv *env, JNFClassInfo *class) { + jclass localCls = NULL; + JNF_ASSERT_COND(class); + localCls = (*env)->FindClass(env, class->name); + if (localCls == NULL) [JNFException raiseUnnamedException:env]; + + class->cls = JNFNewGlobalRef(env, localCls); + (*env)->DeleteLocalRef(env, localCls); + if (class->cls == NULL) [JNFException raiseUnnamedException:env]; +} + +static void JNFLookupMemberID(JNIEnv *env, JNFMemberInfo *member) { + JNF_ASSERT_COND(member); + JNF_ASSERT_COND(member->classInfo); + + if (member->classInfo->cls == NULL) JNFLookupClass(env, member->classInfo); + + if (isMethod(member)) { + member->j.methodID = member->isStatic ? + (*env)->GetStaticMethodID(env, member->classInfo->cls, member->name, member->sig) : + (*env)->GetMethodID(env, member->classInfo->cls, member->name, member->sig); + } else { // This member is a field + member->j.fieldID = member->isStatic ? + (*env)->GetStaticFieldID(env, member->classInfo->cls, member->name, member->sig) : + (*env)->GetFieldID(env, member->classInfo->cls, member->name, member->sig); + } + + // If NULL, then exception occurred + if (member->j.methodID == NULL) [JNFException raiseUnnamedException:env]; +} + +BOOL JNFIsInstanceOf(JNIEnv *env, jobject obj, JNFClassInfo *clazz) { + if (clazz->cls == NULL) JNFLookupClass(env, clazz); + return (BOOL)(*env)->IsInstanceOf(env, obj, clazz->cls); +} + +// +// A whole mess o' macros +// +// All of these macros provide caching of class refs and member IDs, +// which speeds all member accesses following the first. In addition, +// Java exceptions are caught and turned into NSExceptions, +// so error handling is much easier on the native side. +// +// You can use these if you need them for optimization, but it's generally +// easier and better to use the utility functions in jni_utilities.h. + +// LOOKUP_MEMBER_ID - Lookup a fieldID if needed +// +// Give this macro a JNFMemberInfo*, and it will +// lookup and cache the field ID as needed. +#define LOOKUP_MEMBER_ID(env, member) \ +if (member->j.fieldID == NULL) \ +JNFLookupMemberID(env, member) + +#ifdef RAWT_DEBUG +#define VERIFY_MEMBERS +#endif + +#ifdef VERIFY_MEMBERS +#define VERIFY_FIELD(env, obj, field, sig) VerifyMember(env, obj, field, sig, NO) +#define VERIFY_METHOD(env, obj, method, sig) VerifyMember(env, obj, method, sig, YES) +#else +#define VERIFY_FIELD(env, obj, member, sig) +#define VERIFY_METHOD(env, obj, member, sig) +#endif /* RAWT_DEBUG */ + +// GET_FIELD - Safe way to get a java field +// +// This macro takes care of the caching and exception +// propgation. +#define GET_FIELD(env, obj, field, result, sig, jni_call) \ + LOOKUP_MEMBER_ID(env, field); \ + VERIFY_FIELD(env, obj, field, sig); \ + result = (*env)->jni_call(env, obj, field->j.fieldID); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// GET_STATIC_FIELD +// +#define GET_STATIC_FIELD(env, field, result, sig, jni_call) \ + LOOKUP_MEMBER_ID(env, field); \ + VERIFY_FIELD(env, NULL, field, sig); \ + result = (*env)->jni_call(env, field->classInfo->cls, field->j.fieldID); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// SET_FIELD - Safe way to set a java field +// +// Similar to GET_FIELD +#define SET_FIELD(env, obj, field, val, sig, jni_call) \ + LOOKUP_MEMBER_ID(env, field); \ + VERIFY_FIELD(env, obj, field, sig); \ + (*env)->jni_call(env, obj, field->j.fieldID, val); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// SET_STATIC_FIELD +// +#define SET_STATIC_FIELD(env, field, val, sig, jni_call) \ + LOOKUP_MEMBER_ID(env, field); \ + VERIFY_FIELD(env, NULL, field, sig); \ + (*env)->jni_call(env, field->classInfo->cls, field->j.fieldID, val); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// CALL_VOID_METHOD +// +// "args" is a va_list +#define CALL_VOID_METHOD(env, obj, method, jni_call, args) \ + LOOKUP_MEMBER_ID(env, method); \ + VERIFY_METHOD(env, obj, method, JVM_SIGNATURE_VOID); \ + (*env)->jni_call(env, obj, method->j.methodID, args); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// CALL_STATIC_VOID_METHOD +// +// "args" is a va_list +#define CALL_STATIC_VOID_METHOD(env, method, jni_call, args) \ + LOOKUP_MEMBER_ID(env, method); \ + VERIFY_METHOD(env, NULL, method, JVM_SIGNATURE_VOID); \ + (*env)->jni_call(env, method->classInfo->cls, method->j.methodID, args); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// CALL_METHOD - Call a method that returns a value +// +// "args" is a va_list +#define CALL_METHOD(env, obj, method, result, sig, jni_call, args ) \ + LOOKUP_MEMBER_ID(env, method); \ + VERIFY_METHOD(env, obj, method, sig); \ + result = (*env)->jni_call(env, obj, method->j.methodID, args); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + +// CALL_STATIC_METHOD - Call a static method that returns a value +// +// "args" is a va_list +#define CALL_STATIC_METHOD(env, method, result, sig, jni_call, args) \ + LOOKUP_MEMBER_ID(env, method); \ + VERIFY_METHOD(env, NULL, method, sig); \ + result = (*env)->jni_call(env, method->classInfo->cls, method->j.methodID, args); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + + +// NEW_OBJECT - Create instances +// +// "constructor" is a JNFMemberInfo* to a constructor method +// "args" is a va_list of arguments +#define NEW_OBJECT(env, constructor, obj, args) \ + LOOKUP_MEMBER_ID(env, constructor); \ + VERIFY_METHOD(env, NULL, constructor, JVM_SIGNATURE_VOID); \ + obj = (*env)->NewObjectV(env, constructor->classInfo->cls, constructor->j.methodID, args); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + + +// +// Non-static getters & setters +#define GET_FIELD_IMPLEMENTATION(type, sig, jni_call) \ + type JNF ## jni_call(JNIEnv *env, jobject obj, JNFMemberInfo* field) { \ + type result; \ + JNF_ASSERT_COND(!field->isStatic); \ + GET_FIELD(env, obj, field, result, sig, jni_call); \ + return result; \ + } + +#define SET_FIELD_IMPLEMENTATION(type, sig, jni_call) \ + void JNF ## jni_call(JNIEnv *env, jobject obj, JNFMemberInfo* field, type val) {\ + JNF_ASSERT_COND(!field->isStatic); \ + SET_FIELD(env, obj, field, val, sig, jni_call); \ + } + +// +// Non-static getter implemenations +GET_FIELD_IMPLEMENTATION(jobject, JVM_SIGNATURE_ENDCLASS, GetObjectField) +GET_FIELD_IMPLEMENTATION(jboolean, JVM_SIGNATURE_BOOLEAN, GetBooleanField) +GET_FIELD_IMPLEMENTATION(jbyte, JVM_SIGNATURE_BYTE, GetByteField) +GET_FIELD_IMPLEMENTATION(jchar, JVM_SIGNATURE_CHAR, GetCharField) +GET_FIELD_IMPLEMENTATION(jshort, JVM_SIGNATURE_SHORT, GetShortField) +GET_FIELD_IMPLEMENTATION(jint, JVM_SIGNATURE_INT, GetIntField) +GET_FIELD_IMPLEMENTATION(jlong, JVM_SIGNATURE_LONG, GetLongField) +GET_FIELD_IMPLEMENTATION(jfloat, JVM_SIGNATURE_FLOAT, GetFloatField) +GET_FIELD_IMPLEMENTATION(jdouble, JVM_SIGNATURE_DOUBLE, GetDoubleField) + +// +// Non-static setter implemenations +SET_FIELD_IMPLEMENTATION(jobject, JVM_SIGNATURE_ENDCLASS, SetObjectField) +SET_FIELD_IMPLEMENTATION(jboolean, JVM_SIGNATURE_BOOLEAN, SetBooleanField) +SET_FIELD_IMPLEMENTATION(jbyte, JVM_SIGNATURE_BYTE, SetByteField) +SET_FIELD_IMPLEMENTATION(jchar, JVM_SIGNATURE_CHAR, SetCharField) +SET_FIELD_IMPLEMENTATION(jshort, JVM_SIGNATURE_SHORT, SetShortField) +SET_FIELD_IMPLEMENTATION(jint, JVM_SIGNATURE_INT, SetIntField) +SET_FIELD_IMPLEMENTATION(jlong, JVM_SIGNATURE_LONG, SetLongField) +SET_FIELD_IMPLEMENTATION(jfloat, JVM_SIGNATURE_FLOAT, SetFloatField) +SET_FIELD_IMPLEMENTATION(jdouble, JVM_SIGNATURE_DOUBLE, SetDoubleField) + +// +// Static getters & setters +#define GET_STATIC_FIELD_IMPLEMENTATION(type, sig, jni_call) \ + type JNF ## jni_call(JNIEnv *env, JNFMemberInfo* field) { \ + type result; \ + JNF_ASSERT_COND(field->isStatic); \ + GET_STATIC_FIELD(env, field, result, sig, jni_call); \ + return result; \ + } + +#define SET_STATIC_FIELD_IMPLEMENTATION(type, sig, jni_call) \ + void JNF ## jni_call(JNIEnv *env, JNFMemberInfo* field, type val) { \ + JNF_ASSERT_COND(field->isStatic); \ + SET_STATIC_FIELD(env, field, val, sig, jni_call); \ + } + +// +// Static getter implementations +GET_STATIC_FIELD_IMPLEMENTATION(jobject, JVM_SIGNATURE_ENDCLASS, GetStaticObjectField) +GET_STATIC_FIELD_IMPLEMENTATION(jboolean, JVM_SIGNATURE_BOOLEAN, GetStaticBooleanField) +GET_STATIC_FIELD_IMPLEMENTATION(jbyte, JVM_SIGNATURE_BYTE, GetStaticByteField) +GET_STATIC_FIELD_IMPLEMENTATION(jchar, JVM_SIGNATURE_CHAR, GetStaticCharField) +GET_STATIC_FIELD_IMPLEMENTATION(jshort, JVM_SIGNATURE_SHORT, GetStaticShortField) +GET_STATIC_FIELD_IMPLEMENTATION(jint, JVM_SIGNATURE_INT, GetStaticIntField) +GET_STATIC_FIELD_IMPLEMENTATION(jlong, JVM_SIGNATURE_LONG, GetStaticLongField) +GET_STATIC_FIELD_IMPLEMENTATION(jfloat, JVM_SIGNATURE_FLOAT, GetStaticFloatField) +GET_STATIC_FIELD_IMPLEMENTATION(jdouble, JVM_SIGNATURE_DOUBLE, GetStaticDoubleField) + +// +// Static setter implemenations +SET_STATIC_FIELD_IMPLEMENTATION(jobject, JVM_SIGNATURE_ENDCLASS, SetStaticObjectField) +SET_STATIC_FIELD_IMPLEMENTATION(jboolean, JVM_SIGNATURE_BOOLEAN, SetStaticBooleanField) +SET_STATIC_FIELD_IMPLEMENTATION(jbyte, JVM_SIGNATURE_BYTE, SetStaticByteField) +SET_STATIC_FIELD_IMPLEMENTATION(jchar, JVM_SIGNATURE_CHAR, SetStaticCharField) +SET_STATIC_FIELD_IMPLEMENTATION(jshort, JVM_SIGNATURE_SHORT, SetStaticShortField) +SET_STATIC_FIELD_IMPLEMENTATION(jint, JVM_SIGNATURE_INT, SetStaticIntField) +SET_STATIC_FIELD_IMPLEMENTATION(jlong, JVM_SIGNATURE_LONG, SetStaticLongField) +SET_STATIC_FIELD_IMPLEMENTATION(jfloat, JVM_SIGNATURE_FLOAT, SetStaticFloatField) +SET_STATIC_FIELD_IMPLEMENTATION(jdouble, JVM_SIGNATURE_DOUBLE, SetStaticDoubleField) + + +// +// Calling instance methods +// +// FIX: if an exception is thrown, va_end is not called. +// On i386, va_end is a null macro. Check on PPC to verify the same. +void JNFCallVoidMethod(JNIEnv *env, jobject obj, JNFMemberInfo* method, ...) { + va_list args; + JNF_ASSERT_COND(!method->isStatic); + va_start(args, method); + CALL_VOID_METHOD(env, obj, method, CallVoidMethodV, args); + va_end(args); +} + +#define CALL_METHOD_IMPLEMENTATION(type, sig, jni_call) \ + type JNF ## jni_call(JNIEnv *env, jobject obj, JNFMemberInfo* method, ...) {\ + type result; \ + va_list args; \ + JNF_ASSERT_COND(!method->isStatic); \ + va_start(args, method); \ + CALL_METHOD(env, obj, method, result, sig, jni_call ## V, args); \ + va_end(args); \ + return result; \ + } + +CALL_METHOD_IMPLEMENTATION(jobject, JVM_SIGNATURE_ENDCLASS, CallObjectMethod) +CALL_METHOD_IMPLEMENTATION(jboolean, JVM_SIGNATURE_BOOLEAN, CallBooleanMethod) +CALL_METHOD_IMPLEMENTATION(jbyte, JVM_SIGNATURE_BYTE, CallByteMethod) +CALL_METHOD_IMPLEMENTATION(jchar, JVM_SIGNATURE_CHAR, CallCharMethod) +CALL_METHOD_IMPLEMENTATION(jshort, JVM_SIGNATURE_SHORT, CallShortMethod) +CALL_METHOD_IMPLEMENTATION(jint, JVM_SIGNATURE_INT, CallIntMethod) +CALL_METHOD_IMPLEMENTATION(jlong, JVM_SIGNATURE_LONG, CallLongMethod) +CALL_METHOD_IMPLEMENTATION(jfloat, JVM_SIGNATURE_FLOAT, CallFloatMethod) +CALL_METHOD_IMPLEMENTATION(jdouble, JVM_SIGNATURE_DOUBLE, CallDoubleMethod) + +// +// Calling static methods +// +// FIX: if an exception is thrown, va_end is not called. +// On i386, va_end is a null macro. Check on PPC to verify the same. +void JNFCallStaticVoidMethod(JNIEnv *env, JNFMemberInfo* method, ...) { + va_list args; + JNF_ASSERT_COND(method->isStatic); + va_start(args, method); + CALL_STATIC_VOID_METHOD(env, method, CallStaticVoidMethodV, args); + va_end(args); +} + +#define CALL_STATIC_METHOD_IMPLEMENTATION(type, sig, jni_call) \ + type JNF ## jni_call(JNIEnv *env, JNFMemberInfo* method, ...) { \ + type result; \ + va_list args; \ + JNF_ASSERT_COND(method->isStatic); \ + va_start(args, method); \ + CALL_STATIC_METHOD(env, method, result, sig, jni_call ## V, args); \ + va_end(args); \ + return result; \ + } + +CALL_STATIC_METHOD_IMPLEMENTATION(jobject, JVM_SIGNATURE_ENDCLASS, CallStaticObjectMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jboolean, JVM_SIGNATURE_BOOLEAN, CallStaticBooleanMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jbyte, JVM_SIGNATURE_BYTE, CallStaticByteMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jchar, JVM_SIGNATURE_CHAR, CallStaticCharMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jshort, JVM_SIGNATURE_SHORT, CallStaticShortMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jint, JVM_SIGNATURE_INT, CallStaticIntMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jlong, JVM_SIGNATURE_LONG, CallStaticLongMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jfloat, JVM_SIGNATURE_FLOAT, CallStaticFloatMethod) +CALL_STATIC_METHOD_IMPLEMENTATION(jdouble, JVM_SIGNATURE_DOUBLE, CallStaticDoubleMethod) + +// +// Creating new object instances +jobject JNFNewObject(JNIEnv *env, JNFMemberInfo* constructor, ...) +{ + jobject newobj; + va_list args; + JNF_ASSERT_COND(!constructor->isStatic); + va_start(args, constructor); + NEW_OBJECT(env, constructor, newobj, args); + va_end(args); + return newobj; +} + +jobjectArray JNFNewObjectArray(JNIEnv *env, JNFClassInfo *clazz, jsize length) +{ + if (clazz->cls == NULL) JNFLookupClass(env, clazz); + JNF_ASSERT_COND(clazz->cls); + jobjectArray newArray = (*env)->NewObjectArray(env, length, clazz->cls, NULL); + JNF_CHECK_AND_RETHROW_EXCEPTION(env); + return newArray; +} + +#define NEW_PRIMITIVE_ARRAY(primitiveArrayType, methodName) \ + primitiveArrayType JNF ## methodName(JNIEnv *env, jsize length) { \ + primitiveArrayType array = (*env)->methodName(env, length); \ + JNF_CHECK_AND_RETHROW_EXCEPTION(env); \ + return array; \ + } + +NEW_PRIMITIVE_ARRAY(jbooleanArray, NewBooleanArray) +NEW_PRIMITIVE_ARRAY(jbyteArray, NewByteArray) +NEW_PRIMITIVE_ARRAY(jcharArray, NewCharArray) +NEW_PRIMITIVE_ARRAY(jshortArray, NewShortArray) +NEW_PRIMITIVE_ARRAY(jintArray, NewIntArray) +NEW_PRIMITIVE_ARRAY(jlongArray, NewLongArray) +NEW_PRIMITIVE_ARRAY(jfloatArray, NewFloatArray) +NEW_PRIMITIVE_ARRAY(jdoubleArray, NewDoubleArray) + + +// Class-related functions + +// A bottleneck for creating global references +jobject JNFNewGlobalRef(JNIEnv *env, jobject obj) +{ + if (!obj) return NULL; + + jobject globalRef = (*env)->NewGlobalRef(env, obj); + if (!globalRef) JNF_CHECK_AND_RETHROW_EXCEPTION(env); + + // JNF_WARN("Created global ref %#08lx to object:", globalRef); + // JNFDumpJavaObject(env, globalRef); + return globalRef; +} + +// A bottleneck for deleting global references. +void JNFDeleteGlobalRef(JNIEnv *env, jobject globalRef) +{ + if (!globalRef) return; + // JNF_WARN("Deleting global ref %#08lx to object:", globalRef); + // JNFDumpJavaObject(env, globalRef); + (*env)->DeleteGlobalRef(env, globalRef); +} + +// A bottleneck for creating weak global references +jobject JNFNewWeakGlobalRef(JNIEnv *env, jobject obj) +{ + if (!obj) return NULL; + + jobject globalRef = (*env)->NewWeakGlobalRef(env, obj); + if (!globalRef) JNF_CHECK_AND_RETHROW_EXCEPTION(env); + + // JNF_WARN("Created global ref %#08lx to object:", globalRef); + // JNFDumpJavaObject(env, globalRef); + return globalRef; +} + +// A bottleneck for deleting weak global references. +void JNFDeleteWeakGlobalRef(JNIEnv *env, jobject globalRef) +{ + if (!globalRef) return; + // JNF_WARN("Deleting global ref %#08lx to object:", globalRef); + // JNFDumpJavaObject(env, globalRef); + (*env)->DeleteWeakGlobalRef(env, globalRef); +} diff --git a/src/JavaNativeFoundation/JNFJObjectWrapper.h b/src/JavaNativeFoundation/JNFJObjectWrapper.h new file mode 100644 index 0000000..58f5f0b --- /dev/null +++ b/src/JavaNativeFoundation/JNFJObjectWrapper.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Simple wrapper classes to hold Java Objects in JNI global references. + * + * This is used to pass Java objects across thread boundries, often through + * -performSelectorOnMainThread invocations. This wrapper properly creates a + * new global ref, and clears it on -dealloc or -finalize, attaching to the + * current VM, attaching the current thread if necessary, releasing the global + * ref, and detaching the thread from the VM if it attached it. + * + * Destruction of this wrapper is expensive if the jobject has not been + * pre-cleared, because it must re-attach to the JVM. + * + * The JNFWeakJObjectWrapper manages a weak global reference which may become + * invalid if the JVM garbage collects the original object. + */ + +#import +#import + +__BEGIN_DECLS + +JNF_EXPORT +@interface JNFJObjectWrapper : NSObject + ++ (JNFJObjectWrapper *) wrapperWithJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env; +- (id) initWithJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env; +- (void) setJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env; // clears any pre-existing global-ref +- (jobject) jObjectWithEnv:(JNIEnv *)env; // returns a new local-ref, must be released with DeleteLocalRef + +@property (readonly, nonatomic, assign) jobject jObject; + +@end + + +JNF_EXPORT +@interface JNFWeakJObjectWrapper : JNFJObjectWrapper { } + ++ (JNFWeakJObjectWrapper *) wrapperWithJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env; + +@end + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFJObjectWrapper.m b/src/JavaNativeFoundation/JNFJObjectWrapper.m new file mode 100644 index 0000000..17eac99 --- /dev/null +++ b/src/JavaNativeFoundation/JNFJObjectWrapper.m @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFJObjectWrapper.h" + +#import "JNFJNI.h" +#import "JNFThread.h" + +@interface JNFJObjectWrapper () +@property (readwrite, nonatomic, assign) jobject jObject; +@end + +@implementation JNFJObjectWrapper + +- (jobject) _getWithEnv:(__unused JNIEnv *)env { + return self.jObject; +} + +- (jobject) _createObj:(jobject)jObjectIn withEnv:(JNIEnv *)env { + return JNFNewGlobalRef(env, jObjectIn); +} + +- (void) _destroyObj:(jobject)jObjectIn withEnv:(JNIEnv *)env { + JNFDeleteGlobalRef(env, jObjectIn); +} + ++ (JNFJObjectWrapper *) wrapperWithJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env { + return [[[JNFJObjectWrapper alloc] initWithJObject:jObjectIn withEnv:env] autorelease]; +} + +- (id) initWithJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env { + self = [super init]; + if (!self) return self; + + if (jObjectIn) { + self.jObject = [self _createObj:jObjectIn withEnv:env]; + } + + return self; +} + +- (jobject) jObjectWithEnv:(JNIEnv *)env { + jobject validObj = [self _getWithEnv:env]; + if (!validObj) return NULL; + + return (*env)->NewLocalRef(env, validObj); +} + +- (void) setJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env { + jobject const jobj = self.jObject; + if (jobj == jObjectIn) return; + + if (jobj) { + [self _destroyObj:jobj withEnv:env]; + } + + if (jObjectIn) { + self.jObject = [self _createObj:jObjectIn withEnv:env]; + } else { + self.jObject = NULL; + } +} + +- (void) clearJObjectReference { + jobject const jobj = self.jObject; + if (!jobj) return; + + JNFThreadContext threadContext = JNFThreadDetachImmediately; + JNIEnv *env = JNFObtainEnv(&threadContext); + if (env == NULL) return; // leak? + + [self _destroyObj:jobj withEnv:env]; + self.jObject = NULL; + + JNFReleaseEnv(env, &threadContext); +} + +- (void) dealloc { + [self clearJObjectReference]; + [super dealloc]; +} + +@end + + +@implementation JNFWeakJObjectWrapper + ++ (JNFWeakJObjectWrapper *) wrapperWithJObject:(jobject)jObjectIn withEnv:(JNIEnv *)env { + return [[[JNFWeakJObjectWrapper alloc] initWithJObject:jObjectIn withEnv:env] autorelease]; +} + +- (jobject) _getWithEnv:(JNIEnv *)env { + jobject const jobj = self.jObject; + + if ((*env)->IsSameObject(env, jobj, NULL) == JNI_TRUE) { + self.jObject = NULL; // object went invalid + return NULL; + } + return jobj; +} + +- (jobject) _createObj:(jobject)jObjectIn withEnv:(JNIEnv *)env { + return JNFNewWeakGlobalRef(env, jObjectIn); +} + +- (void) _destroyObj:(jobject)jObjectIn withEnv:(JNIEnv *)env { + JNFDeleteWeakGlobalRef(env, jObjectIn); +} + +@end diff --git a/src/JavaNativeFoundation/JNFNumber.h b/src/JavaNativeFoundation/JNFNumber.h new file mode 100644 index 0000000..0a4683b --- /dev/null +++ b/src/JavaNativeFoundation/JNFNumber.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Functions that convert between number container classes. + */ + +#import + +#import + +__BEGIN_DECLS + +/* + * Converts java.lang.Number to an NSNumber + * NOTE: Return value is auto-released, so if you need to hang on to it, you should retain it. + */ +JNF_EXPORT extern NSNumber *JNFJavaToNSNumber(JNIEnv *env, jobject n); + +/* + * Converts an NSNumber to a java.lang.Number + * Only returns java.lang.Longs or java.lang.Doubles. + * NOTE: This returns a JNI Local Ref. Any code that calls must call DeleteLocalRef with the return value. + */ +JNF_EXPORT extern jobject JNFNSToJavaNumber(JNIEnv *env, NSNumber *n); + +/* + * Converts a java.lang.Boolean constants to the CFBooleanRef constants + */ +JNF_EXPORT extern CFBooleanRef JNFJavaToCFBoolean(JNIEnv* env, jobject b); + +/* + * Converts a CFBooleanRef constants to the java.lang.Boolean constants + */ +JNF_EXPORT extern jobject JNFCFToJavaBoolean(JNIEnv *env, CFBooleanRef b); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFNumber.m b/src/JavaNativeFoundation/JNFNumber.m new file mode 100644 index 0000000..aa83276 --- /dev/null +++ b/src/JavaNativeFoundation/JNFNumber.m @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFNumber.h" +#import "JNFJNI.h" + +static JNF_CLASS_CACHE(sjc_Long, "java/lang/Long"); +static JNF_CLASS_CACHE(sjc_Double, "java/lang/Double"); +static JNF_CLASS_CACHE(sjc_Boolean, "java/lang/Boolean"); + +NSNumber *JNFJavaToNSNumber(JNIEnv* env, jobject n) +{ + if (n == NULL) return nil; + + static JNF_CLASS_CACHE(sjc_Number, "java/lang/Number"); + static JNF_CLASS_CACHE(sjc_Integer, "java/lang/Integer"); + static JNF_CLASS_CACHE(sjc_Float, "java/lang/Float"); + static JNF_CLASS_CACHE(sjc_Byte, "java/lang/Byte"); + static JNF_CLASS_CACHE(sjc_Short, "java/lang/Short"); + + // AWT_THREADING Safe (known object) + if (JNFIsInstanceOf(env, n, &sjc_Integer)) { + static JNF_MEMBER_CACHE(jm_intValue, sjc_Number, "intValue", "()I"); + return [NSNumber numberWithInt:JNFCallIntMethod(env, n, jm_intValue)]; + } else if (JNFIsInstanceOf(env, n, &sjc_Long)) { + static JNF_MEMBER_CACHE(jm_longValue, sjc_Number, "longValue", "()J"); + return [NSNumber numberWithLongLong:JNFCallLongMethod(env, n, jm_longValue)]; + } else if (JNFIsInstanceOf(env, n, &sjc_Float)) { + static JNF_MEMBER_CACHE(jm_floatValue, sjc_Number, "floatValue", "()F"); + return [NSNumber numberWithFloat:JNFCallFloatMethod(env, n, jm_floatValue)]; + } else if (JNFIsInstanceOf(env, n, &sjc_Double)) { + static JNF_MEMBER_CACHE(jm_doubleValue, sjc_Number, "doubleValue", "()D"); + return [NSNumber numberWithDouble:JNFCallDoubleMethod(env, n, jm_doubleValue)]; + } else if (JNFIsInstanceOf(env, n, &sjc_Byte)) { + static JNF_MEMBER_CACHE(jm_byteValue, sjc_Number, "byteValue", "()B"); + return [NSNumber numberWithChar:JNFCallByteMethod(env, n, jm_byteValue)]; + } else if (JNFIsInstanceOf(env, n, &sjc_Short)) { + static JNF_MEMBER_CACHE(jm_shortValue, sjc_Number, "shortValue", "()S"); + return [NSNumber numberWithShort:JNFCallShortMethod(env, n, jm_shortValue)]; + } + + return [NSNumber numberWithInt:0]; +} + +jobject JNFNSToJavaNumber(JNIEnv *env, NSNumber *n) +{ + if (n == nil) return NULL; + + if (CFNumberIsFloatType((CFNumberRef)n)) { + static JNF_CTOR_CACHE(jm_Double, sjc_Double, "(D)V"); + return JNFNewObject(env, jm_Double, [n doubleValue]); // AWT_THREADING Safe (known object) + } else { + static JNF_CTOR_CACHE(jm_Long, sjc_Long, "(J)V"); + return JNFNewObject(env, jm_Long, [n longLongValue]); // AWT_THREADING Safe (known object) + } +} + +CFBooleanRef JNFJavaToCFBoolean(JNIEnv* env, jobject b) +{ + if (b == NULL) return NULL; + if (!JNFIsInstanceOf(env, b, &sjc_Boolean)) return NULL; + static JNF_MEMBER_CACHE(jm_booleanValue, sjc_Boolean, "booleanValue", "()Z"); + return JNFCallBooleanMethod(env, b, jm_booleanValue) ? kCFBooleanTrue : kCFBooleanTrue; +} + +jobject JNFCFToJavaBoolean(JNIEnv *env, CFBooleanRef b) +{ + if (b == NULL) return NULL; + static JNF_STATIC_MEMBER_CACHE(js_TRUE, sjc_Boolean, "TRUE", "java/lang/Boolean"); + static JNF_STATIC_MEMBER_CACHE(js_FALSE, sjc_Boolean, "FALSE", "java/lang/Boolean"); + return JNFGetStaticObjectField(env, (b == kCFBooleanTrue) ? js_TRUE : js_FALSE); +} diff --git a/src/JavaNativeFoundation/JNFObject.h b/src/JavaNativeFoundation/JNFObject.h new file mode 100644 index 0000000..24c1134 --- /dev/null +++ b/src/JavaNativeFoundation/JNFObject.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Functions that access some of the base functionality of java.lang.Object. + */ + +#import + +#import + +__BEGIN_DECLS + +/* + * Returns Object.equals() for the two items + */ +JNF_EXPORT extern BOOL JNFObjectEquals(JNIEnv* env, jobject a, jobject b); + +/* + * Returns Object.toString() as an NSString + */ +JNF_EXPORT extern NSString *JNFObjectToString(JNIEnv *env, jobject obj); + +/* + * Returns Object.getClass().toString() as an NSString. Useful in gdb. + */ +JNF_EXPORT extern NSString *JNFObjectClassName(JNIEnv* env, jobject obj); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFObject.m b/src/JavaNativeFoundation/JNFObject.m new file mode 100644 index 0000000..510e58e --- /dev/null +++ b/src/JavaNativeFoundation/JNFObject.m @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFObject.h" + +#import "JNFJNI.h" +#import "JNFString.h" + +static JNF_CLASS_CACHE(sjc_Object, "java/lang/Object"); + +BOOL JNFObjectEquals(JNIEnv* env, jobject a, jobject b) +{ + if ((a == NULL) && (b == NULL)) return YES; + if ((a == NULL) || (b == NULL)) return NO; + + static JNF_MEMBER_CACHE(jm_equals, sjc_Object, "equals", "(Ljava/lang/Object;)Z"); + return (BOOL)JNFCallBooleanMethod(env, a, jm_equals, b); // AWT_THREADING Safe (!appKit) +} + +NSString *JNFObjectToString(JNIEnv *env, jobject obj) +{ + static JNF_MEMBER_CACHE(jm_toString, sjc_Object, "toString", "()Ljava/lang/String;"); + jobject name = JNFCallObjectMethod(env, obj, jm_toString); // AWT_THREADING Safe (known object) + + id result = JNFJavaToNSString(env, name); + (*env)->DeleteLocalRef(env, name); + return result; +} + +NSString *JNFObjectClassName(JNIEnv* env, jobject obj) +{ + static JNF_MEMBER_CACHE(jm_getClass, sjc_Object, "getClass", "()Ljava/lang/Class;"); + + jobject clz = JNFCallObjectMethod(env, obj, jm_getClass); // AWT_THREADING Safe (known object) + NSString *result = JNFObjectToString(env, clz); + (*env)->DeleteLocalRef(env, clz); + return result; +} diff --git a/src/JavaNativeFoundation/JNFPath.h b/src/JavaNativeFoundation/JNFPath.h new file mode 100644 index 0000000..401bd69 --- /dev/null +++ b/src/JavaNativeFoundation/JNFPath.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Functions that create strings that are in the proper format for holding + * paths in Java and native. + */ + +#import + +#import + +__BEGIN_DECLS + +/* + * Returns a jstring in precomposed UTF16 format that is compatable with Java's + * expectation of the UTF16 format for strings to be displayed. + */ +JNF_EXPORT extern jstring JNFNormalizedJavaStringForPath(JNIEnv *env, NSString *path); + +/* + * Returns an NSString in decomposed UTF16 format that is compatable with HFS's + * expectation of the UTF16 format for file system paths. + * NOTE: this NSString is autoreleased. + */ +JNF_EXPORT extern NSString *JNFNormalizedNSStringForPath(JNIEnv *env, jstring path); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFPath.m b/src/JavaNativeFoundation/JNFPath.m new file mode 100644 index 0000000..608a9e3 --- /dev/null +++ b/src/JavaNativeFoundation/JNFPath.m @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFPath.h" + +#import + +#import "JNFString.h" + +jstring JNFNormalizedJavaStringForPath(JNIEnv *env, NSString *inString) +{ + if (inString == nil) return NULL; + + CFMutableStringRef mutableDisplayName = CFStringCreateMutableCopy(NULL, 0, (CFStringRef)inString); + CFStringNormalize(mutableDisplayName, kCFStringNormalizationFormC); + jstring returnValue = JNFNSToJavaString(env, (NSString *)mutableDisplayName); + CFRelease(mutableDisplayName); + + return returnValue; +} + +NSString *JNFNormalizedNSStringForPath(JNIEnv *env, jstring javaString) +{ + if (javaString == NULL) return nil; + + // We were given a filename, so convert it to a compatible representation for the file system. + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *fileName = JNFJavaToNSString(env, javaString); + const char *compatibleFilename = [fm fileSystemRepresentationWithPath:fileName]; + return [fm stringWithFileSystemRepresentation:compatibleFilename length:strlen(compatibleFilename)]; +} diff --git a/src/JavaNativeFoundation/JNFRunLoop.h b/src/JavaNativeFoundation/JNFRunLoop.h new file mode 100644 index 0000000..e03e2a1 --- /dev/null +++ b/src/JavaNativeFoundation/JNFRunLoop.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Used to perform selectors and blocks in the Java runloop mode. + */ + +#import +#import + +__BEGIN_DECLS + +JNF_EXPORT extern NSString *JNFRunLoopDidStartNotification; + +JNF_EXPORT +@interface JNFRunLoop : NSObject { } + ++ (NSString *)javaRunLoopMode; ++ (void)performOnMainThread:(SEL)aSelector on:(id)target withObject:(id)arg waitUntilDone:(BOOL)wait; +#if __BLOCKS__ ++ (void)performOnMainThreadWaiting:(BOOL)waitUntilDone withBlock:(void (^)(void))block; +#endif + +@end + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFRunLoop.m b/src/JavaNativeFoundation/JNFRunLoop.m new file mode 100644 index 0000000..c3ece5b --- /dev/null +++ b/src/JavaNativeFoundation/JNFRunLoop.m @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFRunLoop.h" + +#import + + +NSString *JNFRunLoopDidStartNotification = @"JNFRunLoopDidStartNotification"; + +static NSString *AWTRunLoopMode = @"AWTRunLoopMode"; +static NSArray *sPerformModes = nil; + +@implementation JNFRunLoop + ++ (void)initialize { + if (sPerformModes) return; + sPerformModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, NSModalPanelRunLoopMode, NSEventTrackingRunLoopMode, AWTRunLoopMode, nil]; +} + ++ (NSString *)javaRunLoopMode { + return AWTRunLoopMode; +} + ++ (void)performOnMainThread:(SEL)aSelector on:(id)target withObject:(id)arg waitUntilDone:(BOOL)waitUntilDone { + [target performSelectorOnMainThread:aSelector withObject:arg waitUntilDone:waitUntilDone modes:sPerformModes]; +} + +#if __BLOCKS__ + ++ (void)_performDirectBlock:(void (^)(void))block { + block(); +} + ++ (void)_performCopiedBlock:(void (^)(void))newBlock { + newBlock(); + Block_release(newBlock); +} + ++ (void)performOnMainThreadWaiting:(BOOL)waitUntilDone withBlock:(void (^)(void))block { + if (waitUntilDone) { + [self performOnMainThread:@selector(_performDirectBlock:) on:self withObject:block waitUntilDone:YES]; + } else { + void (^newBlock)(void) = Block_copy(block); + [self performOnMainThread:@selector(_performCopiedBlock:) on:self withObject:newBlock waitUntilDone:NO]; + } +} + +#endif + +@end diff --git a/src/JavaNativeFoundation/JNFRunnable.h b/src/JavaNativeFoundation/JNFRunnable.h new file mode 100644 index 0000000..1db4e4e --- /dev/null +++ b/src/JavaNativeFoundation/JNFRunnable.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Creates NSInvocations which wrap java.lang.Runnables. + */ + +#import +#import + +__BEGIN_DECLS + +JNF_EXPORT +@interface JNFRunnable : NSObject { } ++ (NSInvocation *) invocationWithRunnable:(jobject)runnable withEnv:(JNIEnv *)env; +#if __BLOCKS__ ++ (void(^)(void)) blockWithRunnable:(jobject)runnable withEnv:(JNIEnv *)env; +#endif +@end + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFRunnable.m b/src/JavaNativeFoundation/JNFRunnable.m new file mode 100644 index 0000000..7df6249 --- /dev/null +++ b/src/JavaNativeFoundation/JNFRunnable.m @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFRunnable.h" +#import "JNFThread.h" +#import "JNFJObjectWrapper.h" + + +static JNF_CLASS_CACHE(jc_Runnable, "java/lang/Runnable"); +static JNF_MEMBER_CACHE(jm_run, jc_Runnable, "run", "()V"); + +@interface JNFRunnableWrapper : JNFJObjectWrapper { } +- (void) invokeRunnable; +@end + +@implementation JNFRunnableWrapper + +- (void) invokeRunnable { + JNFThreadContext ctx = JNFThreadDetachOnThreadDeath | JNFThreadSetSystemClassLoaderOnAttach | JNFThreadAttachAsDaemon; + JNIEnv *env = JNFObtainEnv(&ctx); + JNFCallVoidMethod(env, [self jObjectWithEnv:env], jm_run); + JNFReleaseEnv(env, &ctx); +} + +@end + + +@implementation JNFRunnable + ++ (NSInvocation *) invocationWithRunnable:(jobject)runnable withEnv:(JNIEnv *)env { + SEL sel = @selector(invokeRunnable); + NSMethodSignature *sig = [JNFRunnableWrapper instanceMethodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation retainArguments]; + [invocation setSelector:sel]; + + JNFRunnableWrapper *runnableWrapper = [[JNFRunnableWrapper alloc] initWithJObject:runnable withEnv:env]; + [invocation setTarget:runnableWrapper]; + [runnableWrapper release]; + + return invocation; +} + +#if __BLOCKS__ ++ (void(^)(void)) blockWithRunnable:(jobject)runnable withEnv:(JNIEnv *)env { + JNFJObjectWrapper *runnableWrapper = [JNFJObjectWrapper wrapperWithJObject:runnable withEnv:env]; + + return [[^() { + JNFThreadContext ctx = JNFThreadDetachOnThreadDeath | JNFThreadSetSystemClassLoaderOnAttach | JNFThreadAttachAsDaemon; + JNIEnv *_block_local_env = JNFObtainEnv(&ctx); + JNFCallVoidMethod(env, [runnableWrapper jObjectWithEnv:_block_local_env], jm_run); + JNFReleaseEnv(_block_local_env, &ctx); + } copy] autorelease]; +} +#endif + +@end diff --git a/src/JavaNativeFoundation/JNFString.h b/src/JavaNativeFoundation/JNFString.h new file mode 100644 index 0000000..6a8ba6d --- /dev/null +++ b/src/JavaNativeFoundation/JNFString.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Functions that create NSStrings, UTF16 unichars, or UTF8 chars from java.lang.Strings + */ + +#import + +#import + +__BEGIN_DECLS + +// Returns an NSString given a java.lang.String object +// NOTE: Return value is auto-released, so if you need to hang on to it, you should retain it. +JNF_EXPORT extern NSString *JNFJavaToNSString(JNIEnv *env, jstring javaString); + +// Returns a java.lang.String object as a JNI local ref. +// NOTE: This returns a JNI Local Ref. Any code that calls this should call DeleteLocalRef with the return value. +JNF_EXPORT extern jstring JNFNSToJavaString(JNIEnv *env, NSString *nsString); + +/* + * Gets UTF16 unichars from a Java string, and checks for errors and raises a JNFException if + * the unichars cannot be obtained from Java. + */ +JNF_EXPORT extern const unichar *JNFGetStringUTF16UniChars(JNIEnv *env, jstring javaString); + +/* + * Releases the unichars obtained from JNFGetStringUTF16UniChars() + */ +JNF_EXPORT extern void JNFReleaseStringUTF16UniChars(JNIEnv *env, jstring javaString, const unichar *unichars); + +/* + * Gets UTF8 chars from a Java string, and checks for errors and raises a JNFException if + * the chars cannot be obtained from Java. + */ +JNF_EXPORT extern const char *JNFGetStringUTF8Chars(JNIEnv *env, jstring javaString); + +/* + * Releases the chars obtained from JNFGetStringUTF8Chars() + */ +JNF_EXPORT extern void JNFReleaseStringUTF8Chars(JNIEnv *env, jstring javaString, const char *chars); + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFString.m b/src/JavaNativeFoundation/JNFString.m new file mode 100644 index 0000000..db6d465 --- /dev/null +++ b/src/JavaNativeFoundation/JNFString.m @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFString.h" + +#import "JNFJNI.h" +#import "JNFAssert.h" +#import "debug.h" + +#define STACK_BUFFER_SIZE 64 + +/* + * Utility function to convert java String to NSString. We don't go through intermediate cString + * representation, since we are trying to preserve unicode characters from Java to NSString. + */ +NSString *JNFJavaToNSString(JNIEnv *env, jstring javaString) +{ + // We try very hard to only allocate and memcopy once. + if (javaString == NULL) return nil; + + jsize length = (*env)->GetStringLength(env, javaString); + unichar *buffer = (unichar *)calloc((size_t)length, sizeof(unichar)); + (*env)->GetStringRegion(env, javaString, 0, length, buffer); + NSString *str = (NSString *)CFStringCreateWithCharactersNoCopy(NULL, buffer, length, kCFAllocatorMalloc); + // NSLog(@"%@", str); + return [(NSString *)CFMakeCollectable(str) autorelease]; +} + +/* + * Utility function to convert NSString to Java string. We don't go through intermediate cString + * representation, since we are trying to preserve unicode characters in translation. + */ +jstring JNFNSToJavaString(JNIEnv *env, NSString *nsString) +{ + jstring res = nil; + if (nsString == nil) return NULL; + + unsigned long length = [nsString length]; + unichar *buffer; + unichar stackBuffer[STACK_BUFFER_SIZE]; + if (length > STACK_BUFFER_SIZE) { + buffer = (unichar *)calloc(length, sizeof(unichar)); + } else { + buffer = stackBuffer; + } + + JNF_ASSERT_COND(buffer != NULL); + [nsString getCharacters:buffer]; + res = (*env)->NewString(env, buffer, (jsize)length); + if (buffer != stackBuffer) free(buffer); + return res; +} + +const unichar *JNFGetStringUTF16UniChars(JNIEnv *env, jstring javaString) +{ + const jchar *unichars = NULL; + JNF_ASSERT_COND(javaString != NULL); + unichars = (*env)->GetStringChars(env, javaString, NULL); + if (unichars == NULL) [JNFException raise:env as:kNullPointerException reason:"unable to obtain characters from GetStringChars"]; + return (const unichar *)unichars; +} + +void JNFReleaseStringUTF16UniChars(JNIEnv *env, jstring javaString, const unichar *unichars) +{ + JNF_ASSERT_COND(unichars != NULL); + (*env)->ReleaseStringChars(env, javaString, (const jchar *)unichars); +} + +const char *JNFGetStringUTF8Chars(JNIEnv *env, jstring javaString) +{ + const char *chars = NULL; + JNF_ASSERT_COND(javaString != NULL); + chars = (*env)->GetStringUTFChars(env, javaString, NULL); + if (chars == NULL) [JNFException raise:env as:kNullPointerException reason:"unable to obtain characters from GetStringUTFChars"]; + return chars; +} + +void JNFReleaseStringUTF8Chars(JNIEnv *env, jstring javaString, const char *chars) +{ + JNF_ASSERT_COND(chars != NULL); + (*env)->ReleaseStringUTFChars(env, javaString, (const char *)chars); +} diff --git a/src/JavaNativeFoundation/JNFThread.h b/src/JavaNativeFoundation/JNFThread.h new file mode 100644 index 0000000..f50f801 --- /dev/null +++ b/src/JavaNativeFoundation/JNFThread.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Functions to help obtain a JNIEnv pointer in places where one cannot be passed + * though (callbacks, catagory functions, etc). Use sparingly. + */ + +#import +#import + +__BEGIN_DECLS + +// Options only apply if thread was not already attached to the JVM. +enum { + JNFThreadDetachImmediately = (1 << 1), + JNFThreadDetachOnThreadDeath = (1 << 2), + JNFThreadSetSystemClassLoaderOnAttach = (1 << 3), + JNFThreadAttachAsDaemon = (1 << 4) +}; + +typedef jlong JNFThreadContext; + + +/* + * Attaches the current thread to the Java VM if needed, and obtains a JNI environment + * to interact with the VM. Use a provided JNIEnv pointer for your current thread + * whenever possible, since this method is particularly expensive to the Java VM if + * used repeatedly. + * + * Provide a pointer to a JNFThreadContext to pass to JNFReleaseEnv(). + */ +JNF_EXPORT extern JNIEnv *JNFObtainEnv(JNFThreadContext *context) API_DEPRECATED("This functionality is no longer supported and may stop working in a future version of macOS.", macos(10.10, 10.16)); + +/* + * Release the JNIEnv for this thread, and detaches the current thread from the VM if + * it was not already attached. + */ +JNF_EXPORT extern void JNFReleaseEnv(JNIEnv *env, JNFThreadContext *context) API_DEPRECATED("This functionality is no longer supported and may stop working in a future version of macOS.", macos(10.10, 10.16)); + + +#if __BLOCKS__ + +/* + * Performs the same attach/detach as JNFObtainEnv() and JNFReleaseEnv(), but executes a + * block that accepts the obtained JNIEnv. + */ +typedef void (^JNIEnvBlock)(JNIEnv *); +JNF_EXPORT extern void JNFPerformEnvBlock(JNFThreadContext context, JNIEnvBlock block) API_DEPRECATED("This functionality is no longer supported and may stop working in a future version of macOS.", macos(10.10, 10.16)); + +#endif + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFThread.m b/src/JavaNativeFoundation/JNFThread.m new file mode 100644 index 0000000..c5feb27 --- /dev/null +++ b/src/JavaNativeFoundation/JNFThread.m @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFThread.h" + +#import +#import + +static JavaVM *GetGlobalVM() { // obtains a connection to the current VM + static JavaVM *globalVM; + + if (globalVM != NULL) { + return globalVM; + } + + void *jvmHandle = dlopen("@rpath/libjvm.dylib", RTLD_NOW); + if (!jvmHandle) { + NSLog(@"JavaNativeFoundation: %s: Failed to locate @rpath/libjvm.dylib for JNI_GetCreatedJavaVMs(). A JVM must be loaded before calling this function.", __FUNCTION__); + return NULL; + } + + jint (*_JNI_GetCreatedJavaVMs)(JavaVM **, jsize, jsize *) = dlsym(jvmHandle, "JNI_GetCreatedJavaVMs"); + if (!_JNI_GetCreatedJavaVMs) { + NSLog(@"JavaNativeFoundation: %s: Failed to locate JNI_GetCreatedJavaVMs symbol in @rpath/libjvm.dylib", __FUNCTION__); + return NULL; + } + + JavaVM *vmArray; + jsize numVMs = 0; + if (_JNI_GetCreatedJavaVMs(&vmArray, 1, &numVMs) == 0 && numVMs >= 1) { + globalVM = &vmArray[0]; + } + + if (globalVM == NULL) { + NSLog(@"JavaNativeFoundation: %s: JNI_GetCreatedJavaVMs() failed to get any VM.", __FUNCTION__); + return NULL; + } + + return globalVM; +} + +// private marker to indicate if we need to detach on release +enum { + JNFThreadWillDetachOnRelease = (1 << 12) +}; + +static void setSystemClassLoader(JNIEnv *env) { + // setup the context class loader for this new thread coming into the JVM + JNF_CLASS_CACHE(jc_Thread, "java/lang/Thread"); + JNF_STATIC_MEMBER_CACHE(jm_currentThread, jc_Thread, "currentThread", "()Ljava/lang/Thread;"); + jobject currentThread = JNFCallStaticObjectMethod(env, jm_currentThread); + + JNF_CLASS_CACHE(jc_ClassLoader, "java/lang/ClassLoader"); + JNF_STATIC_MEMBER_CACHE(jm_getSystemClassLoader, jc_ClassLoader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); + jobject systemClassLoader = JNFCallStaticObjectMethod(env, jm_getSystemClassLoader); + + JNF_MEMBER_CACHE(jm_setContextClassLoader, jc_Thread, "setContextClassLoader", "(Ljava/lang/ClassLoader;)V"); + JNFCallVoidMethod(env, currentThread, jm_setContextClassLoader, systemClassLoader); +} + +static JNFThreadContext GetEnvUsingJVM(JavaVM *jvm, void **envPtr, BOOL shouldDetachOnRelease, BOOL setClassLoader, BOOL attachAsDaemon) { + jint status = (*jvm)->GetEnv(jvm, envPtr, JNI_VERSION_1_4); + if (status == JNI_OK) { + // common path + return 0; + } + + if (status != JNI_EDETACHED) { + // can't use JNF_ASSERT macros, since we don't really know if we have an env :( + NSLog(@"JavaNativeFoundation: JNFObtainEnv unable to obtain JNIEnv (%d)", (int)status); + return 0; + } + + // we need to attach + if (attachAsDaemon) { + status = (*jvm)->AttachCurrentThreadAsDaemon(jvm, envPtr, NULL); + } else { + status = (*jvm)->AttachCurrentThread(jvm, envPtr, NULL); + } + + if (status != JNI_OK) { + // failed - need to clear our mark to detach, if present + return 0; + } + + if (setClassLoader) { + setSystemClassLoader((JNIEnv *)(*envPtr)); + } + + // by default, we detach at pthread death, but if requested, we will detach on env-release + if (shouldDetachOnRelease) { + return JNFThreadWillDetachOnRelease; + } + + // we don't do anything in this case, because HotSpot on Mac OS X will detach for us. + // Can we install a pthread_atexit handler to detach if we haven't already? + return 0; +} + +// public call to obtain an env, and attach the current thread to the VM in needed +JNIEnv *JNFObtainEnv(JNFThreadContext *context) { + JavaVM *jvm = GetGlobalVM(); + if (!jvm) { + *context = 0; + return NULL; + } + + JNFThreadContext ctx = *context; + BOOL shouldDetachOnRelease = (0 != (ctx & JNFThreadDetachImmediately)); + BOOL setClassLoader = (0 != (ctx & JNFThreadSetSystemClassLoaderOnAttach)); + BOOL attachAsDaemon = (0 != (ctx & JNFThreadAttachAsDaemon)); + + void *env = NULL; + *context = GetEnvUsingJVM(jvm, &env, shouldDetachOnRelease, setClassLoader, attachAsDaemon); + return (JNIEnv *)env; +} + +void JNFReleaseEnv(__unused JNIEnv *env, JNFThreadContext *context) { + if ((*context & JNFThreadWillDetachOnRelease) == 0) { + return; + } + + JavaVM *jvm = GetGlobalVM(); + if (!jvm) return; + + jint status = (*jvm)->DetachCurrentThread(jvm); + if (status != JNI_OK) { + // can't use JNF_ASSERT macros, since we don't really know if we have an env :( + NSLog(@"JavaNativeFoundation: %s: unable to release JNIEnv (%d)", __FUNCTION__, (int)status); + } +} + + +#if __BLOCKS__ + +JNF_EXPORT extern void JNFPerformEnvBlock(JNFThreadContext context, JNIEnvBlock block) { + JNIEnv *env = JNFObtainEnv(&context); + if (env == NULL) [NSException raise:@"Unable to obtain JNIEnv" format:@"Unable to obtain JNIEnv for context: %p", (void *)context]; + + @try { + block(env); + } @finally { + JNFReleaseEnv(env, &context); + } +} + +#endif diff --git a/src/JavaNativeFoundation/JNFTypeCoercion.h b/src/JavaNativeFoundation/JNFTypeCoercion.h new file mode 100644 index 0000000..4dbaa99 --- /dev/null +++ b/src/JavaNativeFoundation/JNFTypeCoercion.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Type Coercion system that translates between Java VM objects and Objective-C Foundation objects. + * + * JNFTypeCoercions are registered into JNFTypeCoercers, which can be chained to other + * JNFTypeCoercers using -deriveCoercer or -initWithParent. If the set of Coercions + * in a Coercer aren't capable of converting an object, the Coercer will delegate up to + * it's parent. + * + * Coercions are registered by Objective-C class and Java class name. If an object is an + * instance of the registered class name, the coercion will be invoked. Default + * implementations for several basic types are provided by JNFDefaultCoercions, and can + * be installed in any order. More specific coercions should be placed farther down + * a coercer chain, and more generic coercions should be placed higher. A Coercer can be + * initialized with a basic Coercion that may want to handle "all cases", like calling + * Object.toString() and -describe on all objects passed to it. + * + * Coercions are passed the Coercion-object that was originally invoked on the + * target object. This permits the lowest level Coercion to be used for subsequent + * object translations for composite objects. The provided List, Map, and Set Coercions + * only handle object hierarchies, and will infinitely recurse if confronted with a + * cycle in the object graph. + * + * Null and nil are both perfectly valid return types for Coercions, and do not indicate + * a failure to coerce an object. Coercers are not thread safe. + */ + +#import +#import + +__BEGIN_DECLS + +@class JNFTypeCoercion; + +JNF_EXPORT +@protocol JNFTypeCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer; +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer; + +@end + + +JNF_EXPORT +@interface JNFTypeCoercer : NSObject + +- (id) init; +- (id) initWithParent:(NSObject *)parentIn; +- (JNFTypeCoercer *) deriveCoercer; +- (void) addCoercion:(NSObject *)coercion forNSClass:(Class)nsClass javaClass:(NSString *)javaClassName; + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env; +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env; + +@end + + +JNF_EXPORT +@interface JNFDefaultCoercions : NSObject { } + ++ (void) addStringCoercionTo:(JNFTypeCoercer *)coercer; ++ (void) addNumberCoercionTo:(JNFTypeCoercer *)coercer; ++ (void) addDateCoercionTo:(JNFTypeCoercer *)coercer; ++ (void) addListCoercionTo:(JNFTypeCoercer *)coercer; ++ (void) addMapCoercionTo:(JNFTypeCoercer *)coercer; ++ (void) addSetCoercionTo:(JNFTypeCoercer *)coercer; + ++ (JNFTypeCoercer *) defaultCoercer; // returns autoreleased copy, not shared, not thread safe + +@end + +__END_DECLS diff --git a/src/JavaNativeFoundation/JNFTypeCoercion.m b/src/JavaNativeFoundation/JNFTypeCoercion.m new file mode 100644 index 0000000..8eae113 --- /dev/null +++ b/src/JavaNativeFoundation/JNFTypeCoercion.m @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "JNFTypeCoercion.h" + +#import "JNFJNI.h" +#import "JNFObject.h" +#import "JNFString.h" +#import "JNFNumber.h" +#import "JNFDate.h" +#import "JNFJObjectWrapper.h" + +// #define DEBUG 1 + +@interface JNFInternalJavaClassToCoersionHolder : JNFJObjectWrapper + +- (id) initWithCoercion:(NSObject *)coercionIn className:(NSString *)className withEnv:(JNIEnv *)env; +- (BOOL) isClassFor:(jobject)obj withEnv:(JNIEnv *)env; +- (NSObject *)coercion; + +@property (nonatomic, readwrite, strong) NSObject *coercion; + +@end + +@interface JNFTypeCoercer () + +@property (nonatomic, readwrite, strong) NSObject *parent; +@property (nonatomic, readwrite, strong) NSMutableDictionary *nsClassToCoercion; +@property (nonatomic, readwrite, strong) NSMutableDictionary *javaClassNameToCoercion; +@property (nonatomic, readwrite, strong) NSArray *javaClassToCoercion; + +@end + +@implementation JNFTypeCoercer + +- (id) init { + return [self initWithParent:nil]; +} + +- (id) initWithParent:(NSObject *)parentIn { + self = [super init]; + if (!self) return self; + + self.parent = parentIn; + self.nsClassToCoercion = [NSMutableDictionary dictionary]; + self.javaClassNameToCoercion = [NSMutableDictionary dictionary]; + self.javaClassToCoercion = nil; + + return self; +} + +- (void) dealloc { + self.parent = nil; + self.nsClassToCoercion = nil; + self.javaClassNameToCoercion = nil; + self.javaClassToCoercion = nil; + + [super dealloc]; +} + +- (JNFTypeCoercer *) deriveCoercer { + return [[[JNFTypeCoercer alloc] initWithParent:self] autorelease]; +} + +- (void) addCoercion:(NSObject *)coercion forNSClass:(Class)nsClass javaClass:(NSString *)javaClassName { + if (nsClass) [self.nsClassToCoercion setObject:coercion forKey:(id)nsClass]; + if (javaClassName) [self.javaClassNameToCoercion setObject:coercion forKey:javaClassName]; + self.javaClassToCoercion = nil; // invalidate Java Class cache +} + +- (NSObject *) findCoercerForNSObject:(id)obj { + NSMutableDictionary * const nsClassToCoercion = self.nsClassToCoercion; + NSObject *coercer = [nsClassToCoercion objectForKey:[obj class]]; + if (coercer) return coercer; + + NSEnumerator *classes = [nsClassToCoercion keyEnumerator]; + Class clazzIter; + while ((clazzIter = [classes nextObject]) != nil) { + if ([obj isKindOfClass:clazzIter]) { + return [nsClassToCoercion objectForKey:clazzIter]; + } + } + + return self.parent; +} + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + return [[self findCoercerForNSObject:obj] coerceNSObject:obj withEnv:env usingCoercer:coercer]; +} + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env { + return [self coerceNSObject:obj withEnv:env usingCoercer:(JNFTypeCoercion *)self]; +} + + +- (NSArray *) javaClassCacheUsingEnv:(JNIEnv *)env { + NSArray * const javaClassToCoercion = self.javaClassToCoercion; + if (javaClassToCoercion) return javaClassToCoercion; + + NSMutableArray *array = [[NSMutableArray alloc] init]; + + NSMutableDictionary * const javaClassNameToCoercion = self.javaClassNameToCoercion; + NSEnumerator *classNames = [javaClassNameToCoercion keyEnumerator]; + NSString *className; + while ((className = [classNames nextObject]) != nil) { + NSObject *coercion = [javaClassNameToCoercion objectForKey:className]; + JNFInternalJavaClassToCoersionHolder *holder = [[JNFInternalJavaClassToCoersionHolder alloc] initWithCoercion:coercion className:className withEnv:env]; + [array addObject:holder]; + [holder release]; + } + + self.javaClassToCoercion = array; + return [array autorelease]; +} + +- (NSObject *) findCoercerForJavaObject:(jobject)obj withEnv:(JNIEnv *)env { + if (obj == NULL) return nil; + + NSMutableDictionary * const javaClassNameToCoercion = self.javaClassNameToCoercion; + NSString *javaClazzName = JNFObjectClassName(env, obj); + NSObject *coercer = [javaClassNameToCoercion objectForKey:javaClazzName]; + if (coercer) return coercer; + +#ifdef DEBUG + NSLog(@"attempting to find coercer for: %@", javaClazzName); +#endif + + NSArray *javaClassCache = [self javaClassCacheUsingEnv:env]; + NSEnumerator *holderIter = [javaClassCache objectEnumerator]; + JNFInternalJavaClassToCoersionHolder *holder; + while ((holder = [holderIter nextObject]) != nil) { + if ([holder isClassFor:obj withEnv:env]) { + NSObject *coercion = [holder coercion]; + [javaClassNameToCoercion setObject:coercion forKey:javaClazzName]; + return coercion; + } + } + + return self.parent; +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + NSObject *coercion = [self findCoercerForJavaObject:obj withEnv:env]; + id nsObj = [coercion coerceJavaObject:obj withEnv:env usingCoercer:coercer]; + if (nsObj == nil) return [NSNull null]; + return nsObj; +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env { + return [self coerceJavaObject:obj withEnv:env usingCoercer:(JNFTypeCoercion *)self]; +} + +@end + + +@implementation JNFInternalJavaClassToCoersionHolder + +- (id) initWithCoercion:(NSObject *)coercionIn className:(NSString *)className withEnv:(JNIEnv *)env { + const char *classNameCStr = [className cStringUsingEncoding:NSUTF8StringEncoding]; + jclass clz = (*env)->FindClass(env, classNameCStr); + if (clz == NULL) [JNFException raise:env as:kClassNotFoundException reason:"Unable to create type converter."]; + + self = [super initWithJObject:clz withEnv:env]; + if (!self) return self; + + self.coercion = coercionIn; + return self; +} + +- (BOOL)isEqual:(id)object { + jclass javaClazz = [self jObject]; + + if ([object isKindOfClass:[self class]]) { + return [((JNFInternalJavaClassToCoersionHolder *)object) jObject] == javaClazz; + } + + return NO; +} + +- (void) dealloc { + self.coercion = nil; + [super dealloc]; +} + +- (NSUInteger)hash { + jclass javaClazz = [self jObject]; + return (NSUInteger)ptr_to_jlong(javaClazz); +} + +- (BOOL) isClassFor:(jobject)obj withEnv:(JNIEnv *)env { + if (obj == NULL) return JNI_FALSE; +#ifdef DEBUG + NSLog(@"is (%@(%@)) a kind of (%@)?", JNFToString(env, obj), JNFClassName(env, obj), JNFToString(env, javaClazz)); +#endif + + jclass javaClazz = [self jObject]; + return (BOOL)(*env)->IsInstanceOf(env, obj, javaClazz); +} + +@end + + +@interface JNFStringCoercion : NSObject { } +@end + +@implementation JNFStringCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(__unused JNFTypeCoercion *)coercer { + return JNFNSToJavaString(env, (NSString *)obj); +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(__unused JNFTypeCoercion *)coercer { + return JNFJavaToNSString(env, (jstring)obj); +} + +@end + + +@interface JNFNumberCoercion : NSObject { } +@end + +@implementation JNFNumberCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(__unused JNFTypeCoercion *)coercer { + return JNFNSToJavaNumber(env, (NSNumber *)obj); +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(__unused JNFTypeCoercion *)coercer { + return JNFJavaToNSNumber(env, obj); +} + +@end + + +@interface JNFDateCoercion : NSObject { } +@end + +@implementation JNFDateCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(__unused JNFTypeCoercion *)coercer { + return JNFNSToJavaCalendar(env, (NSDate *)obj); +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(__unused JNFTypeCoercion *)coercer { + return JNFJavaToNSDate(env, obj); +} + +@end + + +static JNF_CLASS_CACHE(jc_Iterator, "java/util/Iterator"); +static JNF_MEMBER_CACHE(jm_Iterator_hasNext, jc_Iterator, "hasNext", "()Z"); +static JNF_MEMBER_CACHE(jm_Iterator_next, jc_Iterator, "next", "()Ljava/lang/Object;"); + +@interface JNFMapCoercion : NSObject { } +@end + +@implementation JNFMapCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + static JNF_CLASS_CACHE(jc_HashMap, "java/util/HashMap"); + static JNF_CTOR_CACHE(jm_HashMap_ctor, jc_HashMap, "()V"); + static JNF_MEMBER_CACHE(jm_HashMap_put, jc_HashMap, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + NSDictionary *nsDict = (NSDictionary *)obj; + NSEnumerator *keyEnum = [nsDict keyEnumerator]; + + jobject jHashMap = JNFNewObject(env, jm_HashMap_ctor); + + id key; + while ((key = [keyEnum nextObject]) != nil) { + jobject jkey = [coercer coerceNSObject:key withEnv:env usingCoercer:coercer]; + + id value = [nsDict objectForKey:key]; + jobject java_value = [coercer coerceNSObject:value withEnv:env usingCoercer:coercer]; + + JNFCallObjectMethod(env, jHashMap, jm_HashMap_put, jkey, java_value); + + if (jkey != NULL) (*env)->DeleteLocalRef(env, jkey); + if (java_value != NULL) (*env)->DeleteLocalRef(env, java_value); + } + + return jHashMap; +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + static JNF_CLASS_CACHE(jc_Map, "java/util/Map"); + static JNF_MEMBER_CACHE(jm_Map_keySet, jc_Map, "keySet", "()Ljava/util/Set;"); + static JNF_MEMBER_CACHE(jm_Map_get, jc_Map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + + static JNF_CLASS_CACHE(jc_Set, "java/util/Set"); + static JNF_MEMBER_CACHE(jm_Set_iterator, jc_Set, "iterator", "()Ljava/util/Iterator;"); + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + jobject jKeySet = JNFCallObjectMethod(env, obj, jm_Map_keySet); + jobject jKeyIter = JNFCallObjectMethod(env, jKeySet, jm_Set_iterator); + if (jKeySet != NULL) (*env)->DeleteLocalRef(env, jKeySet); + + while (JNFCallBooleanMethod(env, jKeyIter, jm_Iterator_hasNext)) { + jobject jkey = JNFCallObjectMethod(env, jKeyIter, jm_Iterator_next); + id nsKey = [coercer coerceJavaObject:jkey withEnv:env usingCoercer:coercer]; + + jobject java_value = JNFCallObjectMethod(env, obj, jm_Map_get, jkey); + if (jkey != NULL) (*env)->DeleteLocalRef(env, jkey); + + id nsValue = [coercer coerceJavaObject:java_value withEnv:env usingCoercer:coercer]; + if (java_value != NULL) (*env)->DeleteLocalRef(env, java_value); + + [dict setObject:nsValue forKey:nsKey]; + } + + return [dict autorelease]; +} + +@end + + +@interface JNFListCoercion : NSObject { } +@end + +@implementation JNFListCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + static JNF_CLASS_CACHE(jc_List, "java/util/ArrayList"); + static JNF_CTOR_CACHE(jm_List_ctor, jc_List, "(I)V"); + static JNF_MEMBER_CACHE(jm_List_add, jc_List, "add", "(Ljava/lang/Object;)Z"); + + NSArray *nsArray = (NSArray *)obj; + unsigned long count = [nsArray count]; + + jobject javaArray = JNFNewObject(env, jm_List_ctor, (jint)count); + + unsigned long i; + for (i = 0; i < count; i++) { + id iThObj = [nsArray objectAtIndex:i]; + jobject iThJObj = [coercer coerceNSObject:iThObj withEnv:env usingCoercer:coercer]; + JNFCallBooleanMethod(env, javaArray, jm_List_add, iThJObj); + if (iThJObj != NULL) (*env)->DeleteLocalRef(env, iThJObj); + } + + return javaArray; +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + static JNF_CLASS_CACHE(jc_List, "java/util/List"); + static JNF_MEMBER_CACHE(jm_List_iterator, jc_List, "iterator", "()Ljava/util/Iterator;"); + + jobject jIterator = JNFCallObjectMethod(env, obj, jm_List_iterator); + + NSMutableArray *nsArray = [[NSMutableArray alloc] init]; + while (JNFCallBooleanMethod(env, jIterator, jm_Iterator_hasNext)) { + jobject jobj = JNFCallObjectMethod(env, jIterator, jm_Iterator_next); + id nsObj = [coercer coerceJavaObject:jobj withEnv:env usingCoercer:coercer]; + if (jobj != NULL) (*env)->DeleteLocalRef(env, jobj); + [nsArray addObject:nsObj]; + } + + return [nsArray autorelease]; +} + +@end + + +@interface JNFSetCoercion : NSObject { } +@end + +@implementation JNFSetCoercion + +- (jobject) coerceNSObject:(id)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + static JNF_CLASS_CACHE(jc_Set, "java/util/HashSet"); + static JNF_CTOR_CACHE(jm_Set_ctor, jc_Set, "()V"); + static JNF_MEMBER_CACHE(jm_Set_add, jc_Set, "add", "(Ljava/lang/Object;)Z"); + + NSSet *nsSet = (NSSet *)obj; + NSEnumerator *enumerator = [nsSet objectEnumerator]; + + jobject javaSet = JNFNewObject(env, jm_Set_ctor); + id next; + while ((next = [enumerator nextObject]) != nil) { + jobject jnext = [coercer coerceNSObject:next withEnv:env usingCoercer:coercer]; + if (jnext != NULL) { + JNFCallBooleanMethod(env, javaSet, jm_Set_add, jnext); + (*env)->DeleteLocalRef(env, jnext); + } + } + + return javaSet; +} + +- (id) coerceJavaObject:(jobject)obj withEnv:(JNIEnv *)env usingCoercer:(JNFTypeCoercion *)coercer { + static JNF_CLASS_CACHE(jc_Set, "java/util/Set"); + static JNF_MEMBER_CACHE(jm_Set_iterator, jc_Set, "iterator", "()Ljava/util/Iterator;"); + + jobject jIterator = JNFCallObjectMethod(env, obj, jm_Set_iterator); + + NSMutableSet *nsSet = [[NSMutableSet alloc] init]; + while (JNFCallBooleanMethod(env, jIterator, jm_Iterator_hasNext)) { + jobject jobj = JNFCallObjectMethod(env, jIterator, jm_Iterator_next); + if (jobj != NULL) { + id nsObj = [coercer coerceJavaObject:jobj withEnv:env usingCoercer:coercer]; + (*env)->DeleteLocalRef(env, jobj); + [nsSet addObject:nsObj]; + } + } + + return [nsSet autorelease]; +} + +@end + + +@implementation JNFDefaultCoercions + ++ (void) addStringCoercionTo:(JNFTypeCoercer *)coercer { + [coercer addCoercion:[[[JNFStringCoercion alloc] init] autorelease] forNSClass:[NSString class] javaClass:@"java/lang/String"]; +} + ++ (void) addNumberCoercionTo:(JNFTypeCoercer *)coercer { + [coercer addCoercion:[[[JNFNumberCoercion alloc] init] autorelease] forNSClass:[NSNumber class] javaClass:@"java/lang/Number"]; +} + ++ (void) addDateCoercionTo:(JNFTypeCoercer *)coercer { + id dateCoercion = [[[JNFDateCoercion alloc] init] autorelease]; + [coercer addCoercion:dateCoercion forNSClass:[NSDate class] javaClass:@"java/util/Calendar"]; + [coercer addCoercion:dateCoercion forNSClass:[NSDate class] javaClass:@"java/util/Date"]; +} + ++ (void) addListCoercionTo:(JNFTypeCoercer *)coercer { + [coercer addCoercion:[[[JNFListCoercion alloc] init] autorelease] forNSClass:[NSArray class] javaClass:@"java/util/List"]; +} + ++ (void) addMapCoercionTo:(JNFTypeCoercer *)coercer { + [coercer addCoercion:[[[JNFMapCoercion alloc] init] autorelease] forNSClass:[NSDictionary class] javaClass:@"java/util/Map"]; +} + ++ (void) addSetCoercionTo:(JNFTypeCoercer *)coercer { + [coercer addCoercion:[[[JNFSetCoercion alloc] init] autorelease] forNSClass:[NSSet class] javaClass:@"java/util/Set"]; +} + ++ (JNFTypeCoercer *) defaultCoercer { + JNFTypeCoercer *coercer = [[[JNFTypeCoercer alloc] initWithParent:nil] autorelease]; + + [JNFDefaultCoercions addStringCoercionTo:coercer]; + [JNFDefaultCoercions addNumberCoercionTo:coercer]; + [JNFDefaultCoercions addDateCoercionTo:coercer]; + [JNFDefaultCoercions addListCoercionTo:coercer]; + [JNFDefaultCoercions addMapCoercionTo:coercer]; + [JNFDefaultCoercions addSetCoercionTo:coercer]; + + return coercer; +} + +@end diff --git a/src/JavaNativeFoundation/JavaNativeFoundation-Info.plist b/src/JavaNativeFoundation/JavaNativeFoundation-Info.plist new file mode 100644 index 0000000..3d57226 --- /dev/null +++ b/src/JavaNativeFoundation/JavaNativeFoundation-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2008-2020 Apple Inc.\nAll Rights Reserved. + + diff --git a/src/JavaNativeFoundation/JavaNativeFoundation.h b/src/JavaNativeFoundation/JavaNativeFoundation.h new file mode 100644 index 0000000..e7e66d4 --- /dev/null +++ b/src/JavaNativeFoundation/JavaNativeFoundation.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * This umbrella header should be included by all JNI/Cocoa source files + * for easy building. Use this file instead of importing individual JNF + * headers. + */ + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/src/JavaNativeFoundation/Modules/module.modulemap b/src/JavaNativeFoundation/Modules/module.modulemap new file mode 100644 index 0000000..bde3b31 --- /dev/null +++ b/src/JavaNativeFoundation/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module JavaNativeFoundation [extern_c] { + umbrella header "JavaNativeFoundation.h" + export * + module * { export * } +} diff --git a/src/JavaNativeFoundation/debug.h b/src/JavaNativeFoundation/debug.h new file mode 100644 index 0000000..d967795 --- /dev/null +++ b/src/JavaNativeFoundation/debug.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * -- + * + * Internal functions to Java Native Foundation. + */ + +#import +#import + +__BEGIN_DECLS + +// +// Debugging support +// + +// prints a stack trace from the Java VM (can be called from gdb) +void JNFJavaStackTrace(JNIEnv *env); + +// dump some info about a generic Java object. +void JNFDumpJavaObject(JNIEnv *env, jobject obj); + +// prints a Java stack trace into a string +NSString *JNFGetStackTraceAsNSString(JNIEnv *env, jthrowable throwable); + +__END_DECLS diff --git a/src/JavaNativeFoundation/debug.m b/src/JavaNativeFoundation/debug.m new file mode 100644 index 0000000..ab359fd --- /dev/null +++ b/src/JavaNativeFoundation/debug.m @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2008-2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "debug.h" + +#import "JNFAssert.h" +#import "JNFObject.h" + +/* + * Utility function to print the Java stack backtrace. + * In gdb, if you have a value for the JNIEnv on the thread + * you want to trace, you can do a gdb "print" of this function + * to get the stack trace for the thread. + */ +void JNFJavaStackTrace(JNIEnv *env) { + jthrowable obj_javaException; + if ((obj_javaException = (*env)->ExceptionOccurred(env)) != NULL) (*env)->ExceptionClear(env); + + jclass cls_Thread = (*env)->FindClass(env, "java/lang/Thread"); + jmethodID mid_currentThread = (*env)->GetStaticMethodID(env, cls_Thread, "currentThread", "()Ljava/lang/Thread;"); + jobject obj_currentThread = (*env)->CallStaticObjectMethod(env, cls_Thread, mid_currentThread); + jclass cls_currentThread = (*env)->GetObjectClass(env, obj_currentThread); + jmethodID mid_getName = (*env)->GetMethodID(env, cls_currentThread, "getName", "()Ljava/lang/String;"); + jobject obj_threadName = (*env)->CallObjectMethod(env, obj_currentThread, mid_getName); + (*env)->DeleteLocalRef(env, obj_currentThread); + + const char *threadName = (*env)->GetStringUTFChars(env, obj_threadName, NULL); + JNF_WARN("Stack trace from Java thread \"%s\":", threadName); + (*env)->ReleaseStringUTFChars(env, obj_threadName, threadName); + (*env)->DeleteLocalRef(env, obj_threadName); + + jmethodID mid_dumpStack = (*env)->GetStaticMethodID(env, cls_Thread, "dumpStack", "()V"); + (*env)->CallStaticVoidMethod(env, cls_Thread, mid_dumpStack); + (*env)->DeleteLocalRef(env, cls_Thread); + + if (obj_javaException) (*env)->Throw(env, obj_javaException); +} + +/* + * Utility function to dump some info about a generic Java object. + * To be called from gdb. Like JNFJavaStackTrace, you need to have + * a valid value for the JNIEnv to call this function. + */ +void JNFDumpJavaObject(JNIEnv *env, jobject obj) { + jthrowable obj_javaException; + if ((obj_javaException = (*env)->ExceptionOccurred(env)) != NULL) (*env)->ExceptionClear(env); + + jclass cls_CToolkit = (*env)->FindClass(env, "apple/awt/CToolkit"); + jmethodID mid_dumpObject = (*env)->GetStaticMethodID(env, cls_CToolkit, "dumpObject", "(Ljava/lang/Object;)V"); + (*env)->CallStaticVoidMethod(env, cls_CToolkit, mid_dumpObject, obj); + (*env)->DeleteLocalRef(env, cls_CToolkit); + + if (obj_javaException) (*env)->Throw(env, obj_javaException); +} + +/* + * Utility function to print a Java stack trace into a string + */ +NSString *JNFGetStackTraceAsNSString(JNIEnv *env, jthrowable throwable) { + // Writer writer = new StringWriter(); + JNF_CLASS_CACHE(jc_StringWriter, "java/io/StringWriter"); + JNF_CTOR_CACHE(jct_StringWriter, jc_StringWriter, "()V"); + jobject writer = JNFNewObject(env, jct_StringWriter); + + // PrintWriter printWriter = new PrintWriter(writer); + JNF_CLASS_CACHE(jc_PrintWriter, "java/io/PrintWriter"); + JNF_CTOR_CACHE(jct_PrintWriter, jc_PrintWriter, "(Ljava/io/Writer;)V"); + jobject printWriter = JNFNewObject(env, jct_PrintWriter, writer); + + // throwable.printStackTrace(printWriter); + JNF_CLASS_CACHE(jc_Throwable, "java/lang/Throwable"); + JNF_MEMBER_CACHE(jm_printStackTrace, jc_Throwable, "printStackTrace", "(Ljava/io/PrintWriter;)V"); + JNFCallVoidMethod(env, throwable, jm_printStackTrace, printWriter); + (*env)->DeleteLocalRef(env, printWriter); + + // return writer.toString(); + NSString *stackTraceAsString = JNFObjectToString(env, writer); + (*env)->DeleteLocalRef(env, writer); + return stackTraceAsString; +}