├── src ├── test │ ├── cpp │ │ ├── JavaThreadUtilsTest.cpp │ │ ├── JavaThreadUtilsTest.h │ │ ├── CMakeLists.txt │ │ ├── JniHelpersTest.h │ │ ├── JavaExceptionUtilsTest.h │ │ ├── PersistedObject.cpp │ │ ├── JniHelpersTest.cpp │ │ ├── PersistedObject.h │ │ ├── NativeObjectTest.h │ │ ├── JavaExceptionUtilsTest.cpp │ │ ├── JavaClassUtilsTest.h │ │ ├── ByteArrayTest.h │ │ ├── JavaStringTest.h │ │ ├── JavaStringArrayTest.h │ │ ├── JavaClassTest.h │ │ ├── ClassRegistryTest.h │ │ ├── TestObject.h │ │ ├── TestObject.cpp │ │ ├── JavaStringTest.cpp │ │ ├── JavaClassUtilsTest.cpp │ │ ├── JUnitUtils.h │ │ ├── NativeObjectTest.cpp │ │ ├── ByteArrayTest.cpp │ │ ├── JavaStringArrayTest.cpp │ │ └── ClassRegistryTest.cpp │ ├── data │ │ ├── CMakeLists.txt │ │ └── TestConstants.txt │ ├── java │ │ └── com │ │ │ └── spotify │ │ │ └── jni │ │ │ ├── JavaThreadUtilsTest.java │ │ │ ├── PersistedObject.java │ │ │ ├── JavaStringArrayTest.java │ │ │ ├── ClassRegistryTest.java │ │ │ ├── JavaClassUtilsTest.java │ │ │ ├── JavaExceptionUtilsTest.java │ │ │ ├── ByteArrayTest.java │ │ │ ├── TestObject.java │ │ │ ├── JavaClassTest.java │ │ │ ├── JavaStringTest.java │ │ │ └── NativeObjectTest.java │ └── bin │ │ └── generate_test_constants.sh └── main │ ├── cpp │ ├── CMakeLists.txt │ ├── JniHelpers.cpp │ ├── JniHelpers.h │ ├── JavaThreadUtils.h │ ├── JavaExceptionUtils.h │ ├── JavaString.h │ ├── JavaString.cpp │ ├── ScopedPtr.h │ ├── JniGlobalRef.h │ ├── JniWeakGlobalRef.h │ ├── JniLocalRef.h │ ├── ClassRegistry.cpp │ ├── JavaThreadUtils.cpp │ ├── ByteArray.cpp │ ├── ShortArray.cpp │ ├── JniTypes.h │ ├── JniHelpersCommon.h │ ├── JavaStringArray.cpp │ ├── ByteArray.h │ ├── ShortArray.h │ ├── JavaExceptionUtils.cpp │ ├── JavaStringArray.h │ ├── NativeObject.cpp │ ├── JavaClassUtils.h │ ├── ClassRegistry.h │ └── JavaClassUtils.cpp │ └── java │ └── com │ └── spotify │ └── jni │ ├── annotations │ └── UsedByNativeCode.java │ ├── JniHelpersLoader.java │ └── NativeObject.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── CMakeLists.txt ├── .gitignore ├── gradlew.bat ├── README.md └── gradlew /src/test/cpp/JavaThreadUtilsTest.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/cpp/JavaThreadUtilsTest.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todo/JniHelpers/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Feb 08 15:15:36 CET 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(JniHelpers) 3 | 4 | if(ANDROID) 5 | #set(CMAKE_C_CFLAGS "${CMAKE_C_FLAGS} -D__GXX_EXPERIMENTAL_CXX0X__") 6 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++") 7 | else() 8 | find_package(JNI REQUIRED) 9 | endif(ANDROID) 10 | include_directories(${JNI_INCLUDE_DIRS}) 11 | 12 | add_subdirectory(src/main/cpp) 13 | add_subdirectory(src/test/cpp) 14 | add_subdirectory(src/test/data) 15 | 16 | add_dependencies(JniHelpersTest JniHelpersTestsGenerateData) 17 | -------------------------------------------------------------------------------- /src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Spotify AB 2 | 3 | cmake_minimum_required(VERSION 2.8) 4 | project(JniHelpersLib) 5 | 6 | file(GLOB libJniHelpers_SOURCES *.cpp) 7 | file(GLOB libJniHelpers_HEADERS *.h) 8 | 9 | link_directories(${JNI_LIBRARIES}) 10 | add_library(JniHelpers ${libJniHelpers_SOURCES} ${libJniHelpers_HEADERS}) 11 | 12 | if(${UNIX}) 13 | set(CMAKE_CXX_FLAGS "-fPIC") 14 | set_target_properties(JniHelpers PROPERTIES CMAKE_SHARED_LINKER_FLAGS "-fPIC") 15 | elseif(${WINDOWS}) 16 | set(CMAKE_CXX_FLAGS "/MP /EHsc") 17 | endif(${UNIX}) 18 | -------------------------------------------------------------------------------- /src/test/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Spotify AB 2 | 3 | cmake_minimum_required(VERSION 2.8) 4 | project(JniHelpersTest) 5 | 6 | file(GLOB libJniHelpersTest_SOURCES *.cpp) 7 | file(GLOB libJniHelpersTest_HEADERS *.h) 8 | 9 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../main/cpp) 10 | link_directories(${JNI_LIBRARIES}) 11 | add_library(${PROJECT_NAME} SHARED ${libJniHelpersTest_SOURCES} ${libJniHelpersTest_HEADERS}) 12 | target_link_libraries(${PROJECT_NAME} JniHelpers) 13 | 14 | if(${UNIX}) 15 | set(CMAKE_CXX_FLAGS "-std=c++11") 16 | elseif(${WINDOWS}) 17 | set(CMAKE_CXX_FLAGS "/MP /EHsc") 18 | endif(${UNIX}) 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build products 2 | build/* 3 | **/Debug/* 4 | **/Release/* 5 | .gradle 6 | 7 | # Temporary files 8 | *.swp 9 | *~ 10 | *.o 11 | *.obj 12 | *.so 13 | 14 | # Stuff from various IDE's 15 | .idea/* 16 | *.xcodeproj 17 | nbproject/* 18 | *.perspectivev3 19 | *.pbxuser 20 | *.user 21 | *.sdf 22 | *.opensdf 23 | *.suo 24 | *.log 25 | *.tlog 26 | *.idb 27 | *.pdb 28 | *.sln 29 | *.vcxproj 30 | *.vcxproj.filters 31 | *.lastbuildstate 32 | *.cbp 33 | *.iml 34 | 35 | # CMake 36 | CMakeCache.txt 37 | CMakeScripts/* 38 | **/CMakeScripts/* 39 | CMakeFiles 40 | Makefile 41 | Makefile.in 42 | *.cmake 43 | **/*.dir 44 | classes 45 | TestConstants.h 46 | TestConstants.java 47 | -------------------------------------------------------------------------------- /src/test/data/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Spotify AB 2 | 3 | cmake_minimum_required(VERSION 2.8) 4 | project(JniHelpersTestsData) 5 | 6 | set(TESTS_DIR ${PROJECT_SOURCE_DIR}/..) 7 | set(TEST_CONSTANTS_GENERATOR_COMMAND ${CMAKE_CURRENT_LIST_DIR}/../bin/generate_test_constants.sh) 8 | set(TEST_CONSTANTS_CPP_FILE ${TESTS_DIR}/cpp/TestConstants.h) 9 | set(TEST_CONSTANTS_JAVA_FILE ${TESTS_DIR}/java/com/spotify/jni/TestConstants.java) 10 | set(TEST_CONSTANTS_DATA_FILE ${CMAKE_CURRENT_LIST_DIR}/TestConstants.txt) 11 | 12 | add_custom_command(OUTPUT ${TEST_CONSTANTS_CPP_FILE} ${TEST_CONSTANTS_JAVA_FILE} 13 | DEPENDS ${TEST_CONSTANTS_DATA_FILE} 14 | COMMAND ${TEST_CONSTANTS_GENERATOR_COMMAND} ${TEST_CONSTANTS_DATA_FILE} ${TEST_CONSTANTS_CPP_FILE} ${TEST_CONSTANTS_JAVA_FILE} 15 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 16 | ) 17 | 18 | add_custom_target(JniHelpersTestsGenerateData ALL 19 | DEPENDS ${TEST_CONSTANTS_CPP_FILE} 20 | DEPENDS ${TEST_CONSTANTS_JAVA_FILE} 21 | ) 22 | -------------------------------------------------------------------------------- /src/test/data/TestConstants.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Spotify AB 2 | # 3 | # Common values for tests which pass objects through JNI 4 | # This file is used to generate test constant definitions for both Java and C++ 5 | 6 | # Format is: 7 | # Java type @ Constant name @ Constant value @ [JAVA|CPP] 8 | # 9 | # The optional 4th field is for when Java and C++ can't share the same entry. Note that for C++ 11 10 | # string prefixes this is often not needed because the generator handles the common cases. 11 | 12 | String @ TEST_STRING @ "hello, is it me you're looking for?" 13 | String @ TEST_NULL_CHAR @ "\0" 14 | String @ TEST_STRING_WITH_NULL_CHAR @ "hello \0 world" 15 | String @ TEST_UTF16_STRING @ u"UTF16 hello: \u2018." 16 | String @ TEST_UTF8_STRING @ u8"a \u0915\u093E\u091A\u0902 \u00F6 1 2 3" 17 | int @ TEST_INTEGER @ 42 18 | short @ TEST_SHORT @ 777 19 | boolean @ TEST_BOOLEAN @ true 20 | float @ TEST_FLOAT @ 3.14159f 21 | double @ TEST_FLOAT_TOLERANCE @ 0.01 22 | double @ TEST_DOUBLE @ 2.71828 23 | byte @ TEST_BYTE @ 72 24 | char @ TEST_CHAR @ 'ö' @ JAVA 25 | char @ TEST_CHAR @ L'ö' @ CPP 26 | -------------------------------------------------------------------------------- /src/main/cpp/JniHelpers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JniHelpers.h" 23 | 24 | using namespace spotify::jni; 25 | 26 | JNIEnv* jniHelpersInitialize(JavaVM *jvm) { 27 | return JavaThreadUtils::initialize(jvm); 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/JavaThreadUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | public class JavaThreadUtilsTest { 25 | static { 26 | System.loadLibrary("JniHelpersTest"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/cpp/JniHelpersTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniHelpersTest_h__ 23 | #define __JniHelpersTest_h__ 24 | 25 | #include "JniHelpers.h" 26 | #define PACKAGE "com/spotify/jni" 27 | 28 | using namespace spotify::jni; 29 | extern ClassRegistry gClasses; 30 | 31 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved); 32 | 33 | #endif // __JniHelpersTest_h__ 34 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/jni/annotations/UsedByNativeCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni.annotations; 23 | 24 | /** 25 | * Annotation which indicates that a piece of seemingly-unused Java code 26 | * is actually being referenced by native code via JNI. Since annotations 27 | * cannot subclass each other, this should be used in combination with 28 | * {@link java.lang.SuppressWarnings()}. 29 | */ 30 | public @interface UsedByNativeCode {} 31 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/jni/JniHelpersLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | /** 25 | * If you choose to build JniHelpers as a shared library, then this class can 26 | * serve as a static loader for your project. 27 | */ 28 | final class JniHelpersLoader { 29 | static { 30 | System.loadLibrary("JniHelpers"); 31 | } 32 | 33 | private JniHelpersLoader() { 34 | throw new UnsupportedOperationException("Instantiation of JniHelpersLoader is not allowed"); 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/PersistedObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import com.spotify.jni.annotations.UsedByNativeCode; 25 | 26 | public class PersistedObject extends NativeObject { 27 | int i; 28 | 29 | public PersistedObject() {} 30 | 31 | @SuppressWarnings("UnusedDeclaration") 32 | @UsedByNativeCode 33 | public int getI() { 34 | return i; 35 | } 36 | 37 | @SuppressWarnings("UnusedDeclaration") 38 | @UsedByNativeCode 39 | public void setI(int i) { 40 | this.i = i; 41 | } 42 | 43 | @Override 44 | public native void destroy(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/cpp/JniHelpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniHelpers_h__ 23 | #define __JniHelpers_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | 27 | #include "ByteArray.h" 28 | #include "JavaClass.h" 29 | #include "ClassRegistry.h" 30 | #include "JavaClassUtils.h" 31 | #include "JavaExceptionUtils.h" 32 | #include "JavaString.h" 33 | #include "JavaThreadUtils.h" 34 | #include "JniGlobalRef.h" 35 | #include "JniLocalRef.h" 36 | #include "NativeObject.h" 37 | #include "ShortArray.h" 38 | #include "JavaStringArray.h" 39 | 40 | EXPORT JNIEnv *jniHelpersInitialize(JavaVM *jvm); 41 | EXPORT JNIEnv *JniCurrentEnv(JavaVM *jvm); 42 | 43 | #endif // __JniHelpers_h__ 44 | -------------------------------------------------------------------------------- /src/test/bin/generate_test_constants.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (c) 2014 Spotify AB 3 | 4 | if [ $# -ne 3 ] 5 | then 6 | echo "Usage: $(basename $0) " >&2 7 | exit 1 8 | fi 9 | 10 | TEST_CONSTANTS_FILE=$1 11 | CPP_TEST_CONSTANTS_FILE=$2 12 | JAVA_TEST_CONSTANTS_FILE=$3 13 | 14 | if ! [ -e "$(dirname $CPP_TEST_CONSTANTS_FILE)" ] ; then 15 | mkdir -p "$(dirname $CPP_TEST_CONSTANTS_FILE)" 16 | fi 17 | if ! [ -e "$(dirname $JAVA_TEST_CONSTANTS_FILE)" ] ; then 18 | mkdir -p "$(dirname $JAVA_TEST_CONSTANTS_FILE)" 19 | fi 20 | 21 | TEST_CONSTANTS_DOC='/** 22 | * Common values for tests which pass objects through JNI. 23 | * NB: This file is GENERATED. DO NOT EDIT. 24 | */' 25 | 26 | cat << EOF > ${CPP_TEST_CONSTANTS_FILE} 27 | #ifndef __TestConstants_h__ 28 | #define __TestConstants_h__ 29 | 30 | ${TEST_CONSTANTS_DOC} 31 | 32 | EOF 33 | 34 | cat << EOF > ${JAVA_TEST_CONSTANTS_FILE} 35 | package com.spotify.jni; 36 | 37 | ${TEST_CONSTANTS_DOC} 38 | public class TestConstants { 39 | EOF 40 | 41 | awk -F' @ ' ' 42 | /^[[:space:]]*[^#]/ && $0 !~/^[[:space:]]*$/ { 43 | java_type = $1; 44 | name = $2; 45 | value = $3; 46 | lang = toupper($4); 47 | java_value = value; 48 | if (value ~ /".+"/) { 49 | sub(/^[^\"]*/, "", java_value); 50 | } 51 | if (lang !~ /JAVA/) { 52 | print "#define "name" "value >> "'${CPP_TEST_CONSTANTS_FILE}'"; 53 | } 54 | if (lang !~ /CPP/) { 55 | print " public static final "java_type" "name" = "java_value";" >> "'${JAVA_TEST_CONSTANTS_FILE}'"; 56 | } 57 | } 58 | ' ${TEST_CONSTANTS_FILE} 59 | 60 | cat << EOF >> ${CPP_TEST_CONSTANTS_FILE} 61 | 62 | #endif // __TestConstants_h__ 63 | EOF 64 | 65 | cat << EOF >> ${JAVA_TEST_CONSTANTS_FILE} 66 | } 67 | EOF 68 | -------------------------------------------------------------------------------- /src/main/cpp/JavaThreadUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaThreadUtils_h__ 23 | #define __JavaThreadUtils_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | class JavaThreadUtils { 32 | private: 33 | // Direct instantiation not allowed 34 | JavaThreadUtils() {} 35 | JavaThreadUtils(const JavaThreadUtils&) {} 36 | virtual ~JavaThreadUtils() {} 37 | 38 | public: 39 | static EXPORT JavaVM* getJavaVM(); 40 | static EXPORT JNIEnv* initialize(JavaVM* jvm); 41 | static EXPORT JNIEnv* getEnvForCurrentThread(); 42 | static EXPORT JNIEnv* getEnvForCurrentThread(JavaVM *jvm); 43 | static EXPORT JNIEnv* attachCurrentThreadToJVM(const char* thread_name); 44 | static EXPORT JNIEnv* attachCurrentThreadAsDaemonToJVM(const char* thread_name); 45 | static EXPORT void detatchCurrentThreadFromJVM(); 46 | }; 47 | 48 | } // namespace jni 49 | } // namespace spotify 50 | 51 | #endif // __JavaThreadUtils_h__ 52 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/jni/NativeObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import com.spotify.jni.annotations.UsedByNativeCode; 25 | 26 | public abstract class NativeObject { 27 | /** 28 | * Field used to store a pointer to the native instance allocated on the heap. 29 | * Don't modify this value directly, or else you risk causing segfaults or 30 | * leaking memory. 31 | */ 32 | @SuppressWarnings("UnusedDeclaration") 33 | @UsedByNativeCode 34 | protected long nPtr; 35 | 36 | /** 37 | * This method is used to invoke C++ destructors and free native resources, 38 | * and normally you want to declare it as native in your subclass. 39 | * The native implementation should, in turn, simply invoke the base class' 40 | * destroy() method. For more information, see the documentation 41 | * in the NativeObject.h header file. 42 | */ 43 | public abstract void destroy(); 44 | } 45 | -------------------------------------------------------------------------------- /src/test/cpp/JavaExceptionUtilsTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaExceptionUtilsTest_h__ 23 | #define __JavaExceptionUtilsTest_h__ 24 | 25 | #include "JniHelpersTest.h" 26 | 27 | class JavaExceptionUtilsTest : public JavaClass { 28 | public: 29 | JavaExceptionUtilsTest() : JavaClass() {} 30 | JavaExceptionUtilsTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 31 | ~JavaExceptionUtilsTest() {} 32 | 33 | const char* getCanonicalName() const { 34 | return MAKE_CANONICAL_NAME(PACKAGE, JavaExceptionUtilsTest); 35 | } 36 | 37 | void initialize(JNIEnv *env); 38 | void mapFields() {} 39 | 40 | private: 41 | static void nativeThrowException(JNIEnv *env, jobject javaThis); 42 | static void nativeThrowExceptionWithFormatString(JNIEnv *env, jobject javaThis); 43 | static void nativeThrowRuntimeException(JNIEnv *env, jobject javaThis); 44 | static void nativeThrowExceptionOfType(JNIEnv *env, jobject javaThis); 45 | }; 46 | 47 | #endif // __JavaExceptionUtilsTest_h__ 48 | -------------------------------------------------------------------------------- /src/test/cpp/PersistedObject.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "PersistedObject.h" 23 | 24 | PersistedObject::PersistedObject() : NativeObject(), i(0) {} 25 | 26 | PersistedObject::PersistedObject(JNIEnv *env) : NativeObject(env), i(0) { 27 | initialize(env); 28 | } 29 | 30 | void PersistedObject::initialize(JNIEnv *env) { 31 | setClass(env); 32 | cacheConstructor(env); 33 | cacheField(env, "i", kTypeInt); 34 | #if ANDROID 35 | addNativeMethod("destroy", &PersistedObject::nativeDestroy, kTypeVoid, NULL); 36 | #else 37 | addNativeMethod("destroy", (void*)&PersistedObject::nativeDestroy, kTypeVoid, NULL); 38 | #endif 39 | registerNativeMethods(env); 40 | } 41 | 42 | void PersistedObject::mapFields() { 43 | mapField("i", kTypeInt, &i); 44 | } 45 | 46 | void PersistedObject::nativeDestroy(JNIEnv *env, jobject java_this) { 47 | PersistedObject *object = gClasses.getNativeInstance(env, java_this); 48 | if (object != NULL) { 49 | // NativeObject *native_object = dynamic_cast(object); 50 | object->destroy(env, java_this); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/cpp/JavaExceptionUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaExceptionUtils_h__ 23 | #define __JavaExceptionUtils_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JniLocalRef.h" 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | class JavaExceptionUtils { 32 | private: 33 | // Direct instantiation not allowed 34 | JavaExceptionUtils() {} 35 | JavaExceptionUtils(const JavaExceptionUtils&) {} 36 | virtual ~JavaExceptionUtils() {} 37 | 38 | public: 39 | static EXPORT JniLocalRef newThrowable(JNIEnv *env, const char *message, ...); 40 | 41 | static EXPORT void checkException(JNIEnv *env); 42 | static EXPORT void checkExceptionAndClear(JNIEnv *env); 43 | static EXPORT void throwException(JNIEnv *env, const char *message, ...); 44 | static EXPORT void throwRuntimeException(JNIEnv *env, const char *message, ...); 45 | static EXPORT void throwExceptionOfType(JNIEnv *env, const char *exception_class_name, const char *message, ...); 46 | 47 | protected: 48 | static EXPORT void throwExceptionOfType(JNIEnv *env, const char *exception_class_name, const char *message, va_list arguments); 49 | }; 50 | } 51 | } 52 | 53 | #endif // __JavaExceptionUtils_h__ 54 | -------------------------------------------------------------------------------- /src/test/cpp/JniHelpersTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JniHelpersTest.h" 23 | #include "ByteArrayTest.h" 24 | #include "ClassRegistryTest.h" 25 | #include "JavaClassTest.h" 26 | #include "JavaClassUtilsTest.h" 27 | #include "JavaExceptionUtilsTest.h" 28 | #include "JavaStringTest.h" 29 | #include "JavaThreadUtilsTest.h" 30 | #include "NativeObjectTest.h" 31 | #include "PersistedObject.h" 32 | #include "JavaStringArrayTest.h" 33 | 34 | ClassRegistry gClasses; 35 | 36 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void*) { 37 | LOG_INFO("Initializing JNI"); 38 | JNIEnv *env = jniHelpersInitialize(jvm); 39 | if (env == NULL) { 40 | return -1; 41 | } 42 | 43 | gClasses.add(env, new ByteArrayTest(env)); 44 | gClasses.add(env, new ClassRegistryTest(env)); 45 | gClasses.add(env, new JavaClassTest(env)); 46 | gClasses.add(env, new JavaClassUtilsTest(env)); 47 | gClasses.add(env, new JavaExceptionUtilsTest(env)); 48 | gClasses.add(env, new JavaStringTest(env)); 49 | gClasses.add(env, new NativeObjectTest(env)); 50 | gClasses.add(env, new PersistedObject(env)); 51 | gClasses.add(env, new JavaStringArrayTest(env)); 52 | 53 | LOG_INFO("Initialization complete"); 54 | return JAVA_VERSION; 55 | } 56 | -------------------------------------------------------------------------------- /src/test/cpp/PersistedObject.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __PersistedObject_h__ 23 | #define __PersistedObject_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | /** 31 | * @brief Example of a natively persisted class 32 | * 33 | * Instances of this class can be created on the heap and then leaked, the 34 | * address of the leaked instance is stored in a corresponding Java object 35 | * in a long field. When you want to access the native instance again, it 36 | * is provided automatically when invoking ClassRegistry::newInstance. 37 | * 38 | * In order for JniHelpers to correctly recognize a class as being persisted, 39 | * you must call enablePersistence() in initialize(). 40 | */ 41 | class PersistedObject : public NativeObject { 42 | public: 43 | PersistedObject(); 44 | PersistedObject(JNIEnv *env); 45 | 46 | const char* getCanonicalName() const { 47 | return MAKE_CANONICAL_NAME(PACKAGE, PersistedObject); 48 | } 49 | 50 | void initialize(JNIEnv *env); 51 | void mapFields(); 52 | 53 | static void nativeDestroy(JNIEnv *env, jobject java_this); 54 | 55 | public: 56 | int i; 57 | }; 58 | 59 | #endif // __PersistedObject_h__ 60 | -------------------------------------------------------------------------------- /src/main/cpp/JavaString.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaStringUtils_h__ 23 | #define __JavaStringUtils_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JniLocalRef.h" 27 | #include 28 | 29 | namespace spotify { 30 | namespace jni { 31 | 32 | #if WIN32 33 | // TODO: This is a MSVC thing, should refactor to use PIMPL instead (ugh) 34 | template class EXPORT std::basic_string, std::allocator>; 35 | #endif 36 | 37 | class EXPORT JavaString { 38 | public: 39 | JavaString(); 40 | JavaString(const std::string &string); 41 | JavaString(JNIEnv *env, jstring javaString); 42 | virtual ~JavaString() {} 43 | 44 | const std::string& get() const; 45 | const std::string& operator*() const { return get(); } 46 | 47 | JniLocalRef toJavaString(JNIEnv *env) const; 48 | 49 | void set(const char *value); 50 | void operator=(const char *value) { set(value); } 51 | void set(const std::string &value); 52 | void operator=(const std::string &value) { set(value); } 53 | void set(JNIEnv *env, jstring javaString); 54 | 55 | public: 56 | std::string _value; 57 | }; 58 | 59 | } // namespace jni 60 | } // namespace spotify 61 | 62 | #endif // __JavaStringUtils_h__ 63 | -------------------------------------------------------------------------------- /src/main/cpp/JavaString.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaString.h" 23 | #include "JavaExceptionUtils.h" 24 | 25 | namespace spotify { 26 | namespace jni { 27 | 28 | JavaString::JavaString() { 29 | _value = ""; 30 | } 31 | 32 | JavaString::JavaString(const std::string &string) { 33 | _value = string; 34 | } 35 | 36 | JavaString::JavaString(JNIEnv *env, jstring javaString) { 37 | set(env, javaString); 38 | } 39 | 40 | const std::string& JavaString::get() const { 41 | return _value; 42 | } 43 | 44 | JniLocalRef JavaString::toJavaString(JNIEnv *env) const { 45 | return env->NewStringUTF(_value.c_str()); 46 | } 47 | 48 | void JavaString::set(const char *value) { 49 | _value = value; 50 | } 51 | 52 | void JavaString::set(const std::string &value) { 53 | _value = value; 54 | } 55 | 56 | void JavaString::set(JNIEnv *env, jstring javaString) { 57 | if (javaString == NULL) { 58 | return; 59 | } 60 | 61 | const char *string = env->GetStringUTFChars(javaString, 0); 62 | JavaExceptionUtils::checkException(env); 63 | if (string == NULL) { 64 | return; 65 | } 66 | 67 | _value = string; 68 | env->ReleaseStringUTFChars(javaString, string); 69 | JavaExceptionUtils::checkException(env); 70 | } 71 | 72 | 73 | } // namespace jni 74 | } // namespace spotify 75 | -------------------------------------------------------------------------------- /src/main/cpp/ScopedPtr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __ScopedPtr_h__ 23 | #define __ScopedPtr_h__ 24 | 25 | namespace spotify { 26 | namespace jni { 27 | 28 | /** 29 | * @brief Scoped pointer class 30 | * 31 | * Under normal circumstances it would be preferable to use the scoped 32 | * pointer provided by stl::scoped_ptr, or even boost, however both of 33 | * these libraries are a bit problematic under Android. Since we only 34 | * need the most basic implementation of a scoped pointer, re-inventing 35 | * the wheel is ok (in small doses). 36 | */ 37 | template 38 | class ScopedPtr { 39 | public: 40 | ScopedPtr() : _obj(NULL) {} 41 | ScopedPtr(Type *object) : _obj(NULL) { set(object); } 42 | ScopedPtr(const ScopedPtr &ref) : _obj(NULL) { set(ref.get()); } 43 | 44 | ~ScopedPtr() { set(NULL); } 45 | 46 | Type *get() const { return _obj; } 47 | 48 | void set(Type *object) { 49 | if (_obj) { 50 | delete _obj; 51 | } 52 | _obj = object; 53 | } 54 | 55 | Type *leak() { 56 | Type result = _obj; 57 | _obj = NULL; 58 | return result; 59 | } 60 | 61 | private: 62 | Type *_obj; 63 | }; 64 | 65 | } // namespace jni 66 | } // namespace spotify 67 | 68 | #endif // __ScopedPtr_h__ 69 | -------------------------------------------------------------------------------- /src/main/cpp/JniGlobalRef.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniGlobalRef_h__ 23 | #define __JniGlobalRef_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JavaThreadUtils.h" 27 | #include "JniLocalRef.h" 28 | 29 | namespace spotify { 30 | namespace jni { 31 | 32 | // RAII helper to maintain global references automatically. 33 | template 34 | class EXPORT JniGlobalRef { 35 | public: 36 | JniGlobalRef() : _obj(NULL) {} 37 | JniGlobalRef(const JniGlobalRef &ref) : _obj(NULL) { set(ref.get()); } 38 | JniGlobalRef(const JniLocalRef &ref) : _obj(NULL) { set(ref.get()); } 39 | 40 | ~JniGlobalRef() { set(NULL); } 41 | 42 | JniType get() const { return _obj; } 43 | 44 | void set(JniType obj) { 45 | JNIEnv *env = NULL; 46 | if (_obj || obj) { 47 | env = JavaThreadUtils::getEnvForCurrentThread(); 48 | } 49 | if (_obj) { 50 | if (env) { 51 | env->DeleteGlobalRef(_obj); 52 | } 53 | _obj = NULL; 54 | } 55 | if (obj && env) { 56 | _obj = (JniType)env->NewGlobalRef(obj); 57 | } 58 | } 59 | 60 | operator JniType() const { return _obj; } 61 | 62 | void operator=(const JniLocalRef &ref) { set(ref.get()); } 63 | 64 | private: 65 | JniType _obj; 66 | }; 67 | 68 | } // namespace jni 69 | } // namespace spotify 70 | 71 | #endif // __JniGlobalRef_h__ 72 | -------------------------------------------------------------------------------- /src/test/cpp/NativeObjectTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __NativeObjectTest_h__ 23 | #define __NativeObjectTest_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | class NativeObjectTest : public JavaClass { 31 | public: 32 | NativeObjectTest() : JavaClass() {} 33 | NativeObjectTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 34 | ~NativeObjectTest() {} 35 | 36 | const char* getCanonicalName() const { 37 | return MAKE_CANONICAL_NAME(PACKAGE, NativeObjectTest); 38 | } 39 | void initialize(JNIEnv *env); 40 | void mapFields() {} 41 | 42 | private: 43 | static jobject createPersistedObject(JNIEnv *env, jobject javaThis); 44 | static jobject getPersistedInstance(JNIEnv *env, jobject javaThis, jobject object); 45 | static void nativeIsPersistenceEnabled(JNIEnv *env, jobject javaThis); 46 | static void isPersistenceEnabledWithoutInit(JNIEnv *env, jobject javaThis); 47 | static void destroyPersistedObject(JNIEnv *env, jobject javaThis, jobject object); 48 | static void persistInvalidClass(JNIEnv *env, jobject javaThis); 49 | static void persistNullObject(JNIEnv *env, jobject javaThis); 50 | static void destroyInvalidClass(JNIEnv *env, jobject javaThis); 51 | static void destroyNullObject(JNIEnv *env, jobject javaThis); 52 | }; 53 | 54 | #endif // __NativeObjectTest_h__ 55 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/JavaStringArrayTest.java: -------------------------------------------------------------------------------- 1 | package com.spotify.jni; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | public class JavaStringArrayTest { 8 | 9 | static { 10 | System.loadLibrary("JniHelpersTest"); 11 | } 12 | 13 | private String[] getTestJavaStringArray() { 14 | return new String[] {"abc", "def", "ghi", "ążń"}; 15 | } 16 | 17 | @Test 18 | public native void createNewJavaStringArray() throws Exception; 19 | 20 | @Test 21 | public native void createNewJavaStringArrayWithData() throws Exception; 22 | 23 | @Test 24 | public native void createNewJavaStringArrayWithDataCopy() throws Exception; 25 | 26 | @Test 27 | public void createNewJavaStringArrayWithJavaData() throws Exception { 28 | nativeCreateNewJavaStringArrayWithJavaData(getTestJavaStringArray()); 29 | } 30 | 31 | private native void nativeCreateNewJavaStringArrayWithJavaData(String[] elements); 32 | 33 | @Test 34 | public native void createNewJavaStringArrayWithNull() throws Exception; 35 | 36 | @Test 37 | public native void createNewJavaStringArrayWithNullAndNonZeroLength() throws Exception; 38 | 39 | public native String[] nativeGetTestJavaStringArray(); 40 | 41 | @Test 42 | public void getJavaStringArray() throws Exception { 43 | String[] result = nativeGetTestJavaStringArray(); 44 | String[] expected = getTestJavaStringArray(); 45 | assertEquals(expected.length, result.length); 46 | for (int i = 0; i < result.length; i++) { 47 | assertEquals(expected[i], result[i]); 48 | } 49 | } 50 | 51 | @Test 52 | public native void setData() throws Exception; 53 | 54 | @Test 55 | public native void setDataWithCopy() throws Exception; 56 | 57 | public native void nativeSetJavaStringArray(String[] data, int expectedSize); 58 | 59 | @Test 60 | public void setJavaData() throws Exception { 61 | String[] expected = getTestJavaStringArray(); 62 | nativeSetJavaStringArray(expected, expected.length); 63 | } 64 | 65 | @Test 66 | public void setJavaDataWithNull() throws Exception { 67 | nativeSetJavaStringArray(null, 0); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/cpp/JavaExceptionUtilsTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaExceptionUtilsTest.h" 23 | #include "TestConstants.h" 24 | 25 | void JavaExceptionUtilsTest::initialize(JNIEnv *env) { 26 | setClass(env); 27 | addNativeMethod("nativeThrowException", (void*)nativeThrowException, kTypeVoid, NULL); 28 | addNativeMethod("nativeThrowExceptionWithFormatString", (void*)nativeThrowExceptionWithFormatString, kTypeVoid, NULL); 29 | addNativeMethod("nativeThrowRuntimeException", (void*)nativeThrowRuntimeException, kTypeVoid, NULL); 30 | addNativeMethod("nativeThrowExceptionOfType", (void*)nativeThrowExceptionOfType, kTypeVoid, NULL); 31 | registerNativeMethods(env); 32 | } 33 | 34 | void JavaExceptionUtilsTest::nativeThrowException(JNIEnv *env, jobject javaThis) { 35 | JavaExceptionUtils::throwException(env, TEST_STRING); 36 | } 37 | 38 | void JavaExceptionUtilsTest::nativeThrowExceptionWithFormatString(JNIEnv *env, jobject javaThis) { 39 | JavaExceptionUtils::throwException(env, "%s, %d", TEST_STRING, TEST_INTEGER); 40 | } 41 | 42 | void JavaExceptionUtilsTest::nativeThrowRuntimeException(JNIEnv *env, jobject javaThis) { 43 | JavaExceptionUtils::throwRuntimeException(env, TEST_STRING); 44 | } 45 | 46 | void JavaExceptionUtilsTest::nativeThrowExceptionOfType(JNIEnv *env, jobject javaThis) { 47 | JavaExceptionUtils::throwExceptionOfType(env, kTypeUnsupportedOperationException, TEST_STRING); 48 | } 49 | -------------------------------------------------------------------------------- /src/test/cpp/JavaClassUtilsTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaClassUtilsTest_h__ 23 | #define __JavaClassUtilsTest_h__ 24 | 25 | #include "JniHelpersTest.h" 26 | 27 | class JavaClassUtilsTest : public JavaClass { 28 | public: 29 | JavaClassUtilsTest() : JavaClass() {} 30 | JavaClassUtilsTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 31 | ~JavaClassUtilsTest() {} 32 | 33 | const char* getCanonicalName() const { 34 | return MAKE_CANONICAL_NAME(PACKAGE, JavaClassUtilsTest); 35 | } 36 | 37 | void initialize(JNIEnv *env); 38 | void mapFields() {} 39 | 40 | public: 41 | static jclass findObjectClass(JNIEnv *env, jobject javaThis); 42 | static void findInvalidClass(JNIEnv *env, jobject javaThis); 43 | static jclass nativeFindClassWithLoader(JNIEnv *env, jobject javaThis); 44 | static void findInvalidClassWithLoader(JNIEnv *env, jobject javaThis); 45 | static void makeNameForSignatureWithNull(JNIEnv *env, jobject javaThis); 46 | static void makeNameForSignatureWithPrimitive(JNIEnv *env, jobject javaThis); 47 | static void makeNameForSignatureWithArray(JNIEnv *env, jobject javaThis); 48 | static void makeNameForSignatureWithObject(JNIEnv *env, jobject javaThis); 49 | static void makeNameForSignatureWithJniSignature(JNIEnv *env, jobject javaThis); 50 | static void makeNameForSignatureWithArrayOfObjects(JNIEnv *env, jobject javaThis); 51 | }; 52 | 53 | #endif // __JavaClassUtilsTest_h__ 54 | -------------------------------------------------------------------------------- /src/main/cpp/JniWeakGlobalRef.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniWeakGlobalRef_h__ 23 | #define __JniWeakGlobalRef_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JavaThreadUtils.h" 27 | #include "JniLocalRef.h" 28 | 29 | namespace spotify { 30 | namespace jni { 31 | 32 | // RAII helper to maintain global references automatically. 33 | template 34 | class EXPORT JniWeakGlobalRef { 35 | public: 36 | JniWeakGlobalRef() : _obj(NULL) {} 37 | JniWeakGlobalRef(const JniWeakGlobalRef &ref) : _obj(NULL) { set(ref.get()); } 38 | JniWeakGlobalRef(const JniLocalRef &ref) : _obj(NULL) { set(ref.get()); } 39 | 40 | ~JniWeakGlobalRef() { set(NULL); } 41 | 42 | JniType get() const { return _obj; } 43 | 44 | JniType leak() { 45 | JniType obj = _obj; 46 | _obj = NULL; 47 | return obj; 48 | } 49 | 50 | void set(JniType obj) { 51 | JNIEnv *env = NULL; 52 | if (_obj || obj) { 53 | env = JavaThreadUtils::getEnvForCurrentThread(); 54 | } 55 | if (_obj) { 56 | if (env) { 57 | env->DeleteGlobalRef(_obj); 58 | } 59 | _obj = NULL; 60 | } 61 | if (obj && env) { 62 | _obj = (JniType)env->NewWeakGlobalRef(obj); 63 | } 64 | } 65 | 66 | operator JniType() const { return _obj; } 67 | 68 | void operator=(const JniLocalRef &ref) { set(ref.get()); } 69 | 70 | private: 71 | JniType _obj; 72 | }; 73 | 74 | } // namespace jni 75 | } // namespace spotify 76 | 77 | #endif // __JniWeakGlobalRef_h__ 78 | -------------------------------------------------------------------------------- /src/test/cpp/ByteArrayTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __ByteArrayTest_h__ 23 | #define __ByteArrayTest_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | class ByteArrayTest : public JavaClass { 31 | public: 32 | ByteArrayTest() : JavaClass() {} 33 | ByteArrayTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 34 | ~ByteArrayTest() {} 35 | 36 | const char* getCanonicalName() const { 37 | return MAKE_CANONICAL_NAME(PACKAGE, ByteArrayTest); 38 | } 39 | void initialize(JNIEnv *env); 40 | void mapFields() {} 41 | 42 | private: 43 | static void* getTestData(); 44 | static size_t getTestDataSize(); 45 | 46 | static void createNewByteArray(JNIEnv *env, jobject javaThis); 47 | static void createNewByteArrayWithData(JNIEnv *env, jobject javaThis); 48 | static void createNewByteArrayWithDataCopy(JNIEnv *env, jobject javaThis); 49 | static void nativeCreateNewByteArrayWithJavaData(JNIEnv *env, jobject javaThis, jbyteArray javaData); 50 | static void createNewByteArrayWithNull(JNIEnv *env, jobject javaThis); 51 | static void createNewByteArrayWithNullAndNonZeroLength(JNIEnv *env, jobject javaThis); 52 | static jbyteArray nativeGetTestJavaByteArray(JNIEnv *env, jobject javaThis); 53 | static void setData(JNIEnv *env, jobject javaThis); 54 | static void setDataWithCopy(JNIEnv *env, jobject javaThis); 55 | static void nativeSetJavaByteArray(JNIEnv *env, jobject javaThis, jbyteArray javaData, jint expectedSize); 56 | }; 57 | 58 | #endif // __ByteArrayTest_h__ 59 | -------------------------------------------------------------------------------- /src/test/cpp/JavaStringTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaStringTest_h__ 23 | #define __JavaStringTest_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | class EXPORT JavaStringTest : public JavaClass { 31 | public: 32 | JavaStringTest() : JavaClass() {} 33 | JavaStringTest(JNIEnv *env) : JavaClass(env) { 34 | initialize(env); 35 | } 36 | ~JavaStringTest() {} 37 | 38 | void initialize(JNIEnv *env); 39 | void mapFields() {} 40 | const char* getCanonicalName() const { 41 | return MAKE_CANONICAL_NAME(PACKAGE, JavaStringTest); 42 | } 43 | 44 | private: 45 | static bool supportsRawStringLiterals(JNIEnv *env); 46 | 47 | static void createJavaString(JNIEnv *env, jobject javaThis); 48 | static void createJavaStringFromStdString(JNIEnv *env, jobject javaThis); 49 | static void nativeCreateJavaStringFromJavaString(JNIEnv *env, jobject javaThis, jobject javaString); 50 | 51 | static jstring nativeGetJavaString(JNIEnv *env, jobject javaThis); 52 | static jstring nativeGetJavaStringWithNullChar(JNIEnv *env, jobject javaThis); 53 | static jstring nativeGetJavaStringUtf16(JNIEnv *env, jobject javaThis); 54 | static jstring nativeGetJavaStringUtf8(JNIEnv *env, jobject javaThis); 55 | 56 | static void nativeSetValue(JNIEnv *env, jobject javaThis, jobject javaString); 57 | static void nativeSetValueWithOperator(JNIEnv *env, jobject javaThis); 58 | static jstring nativeSetAndReturnValue(JNIEnv *env, jobject javaThis, jobject javaString); 59 | }; 60 | 61 | #endif // __JavaStringTest_h__ 62 | -------------------------------------------------------------------------------- /src/main/cpp/JniLocalRef.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniLocalRef_h__ 23 | #define __JniLocalRef_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JavaThreadUtils.h" 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | // RAII helper to maintain local references automatically 32 | template 33 | class JniLocalRef { 34 | public: 35 | JniLocalRef() : _obj(NULL) {} 36 | JniLocalRef(JniType obj) : _obj(NULL) { set(obj); } 37 | JniLocalRef(const JniLocalRef &ref) : _obj(NULL) { 38 | if (ref.get()) { 39 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 40 | set(env ? (JniType)env->NewLocalRef(ref.get()): NULL); 41 | } 42 | } 43 | 44 | ~JniLocalRef() { set(NULL); } 45 | 46 | JniType get() const { return _obj; } 47 | void set(JniType obj) { 48 | if (_obj) { 49 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 50 | if (env) env->DeleteLocalRef(_obj); 51 | } 52 | _obj = obj; 53 | } 54 | 55 | JniType leak() { 56 | JniType obj = _obj; 57 | _obj = NULL; 58 | return obj; 59 | } 60 | 61 | operator JniType() const { return _obj; } 62 | 63 | void operator=(JniType obj) { set(obj); } 64 | void operator=(const JniLocalRef &ref) { 65 | if (ref.get()) { 66 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 67 | set(env ? (JniType)env->NewLocalRef(ref.get()) : NULL); 68 | } 69 | else { 70 | set(NULL); 71 | } 72 | } 73 | 74 | private: 75 | JniType _obj; 76 | }; 77 | 78 | } // namespace jni 79 | } // namespace spotify 80 | 81 | #endif // __JniLocalRef_h__ 82 | -------------------------------------------------------------------------------- /src/test/cpp/JavaStringArrayTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaStringArrayTest_h__ 23 | #define __JavaStringArrayTest_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | class JavaStringArrayTest : public JavaClass { 31 | public: 32 | JavaStringArrayTest() : JavaClass() {} 33 | JavaStringArrayTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 34 | ~JavaStringArrayTest() {} 35 | 36 | const char* getCanonicalName() const { 37 | return MAKE_CANONICAL_NAME(PACKAGE, JavaStringArrayTest); 38 | } 39 | void initialize(JNIEnv *env); 40 | void mapFields() {} 41 | 42 | private: 43 | static JavaString **getTestData(); 44 | static size_t getTestDataSize(); 45 | 46 | static void createNewJavaStringArray(JNIEnv *env, jobject javaThis); 47 | static void createNewJavaStringArrayWithData(JNIEnv *env, jobject javaThis); 48 | static void createNewJavaStringArrayWithDataCopy(JNIEnv *env, jobject javaThis); 49 | static void nativeCreateNewJavaStringArrayWithJavaData(JNIEnv *env, jobject javaThis, jobjectArray javaData); 50 | static void createNewJavaStringArrayWithNull(JNIEnv *env, jobject javaThis); 51 | static void createNewJavaStringArrayWithNullAndNonZeroLength(JNIEnv *env, jobject javaThis); 52 | static jobjectArray nativeGetTestJavaStringArray(JNIEnv *env, jobject javaThis); 53 | static void setData(JNIEnv *env, jobject javaThis); 54 | static void setDataWithCopy(JNIEnv *env, jobject javaThis); 55 | static void nativeSetJavaStringArray(JNIEnv *env, jobject javaThis, jobjectArray javaData, jint expectedSize); 56 | }; 57 | 58 | #endif // __JavaStringArrayTest_h__ 59 | -------------------------------------------------------------------------------- /src/test/cpp/JavaClassTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaClassTest_h__ 23 | #define __JavaClassTest_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | class JavaClassTest : public JavaClass { 31 | public: 32 | JavaClassTest() : JavaClass() {} 33 | JavaClassTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 34 | ~JavaClassTest() {} 35 | 36 | const char* getCanonicalName() const { 37 | return MAKE_CANONICAL_NAME(PACKAGE, JavaClassTest); 38 | } 39 | void initialize(JNIEnv *env); 40 | void mapFields() {} 41 | 42 | private: 43 | static void createJavaClass(JNIEnv *env, jobject javaThis); 44 | static void nativeIsInitialized(JNIEnv *env, jobject javaThis); 45 | static void testGetCanonicalName(JNIEnv *env, jobject javaThis); 46 | static void testGetSimpleName(JNIEnv *env, jobject javaThis); 47 | static void testMerge(JNIEnv *env, jobject javaThis); 48 | static void nativeSetJavaObject(JNIEnv *env, jobject javaThis, jobject object); 49 | static jobject nativeToJavaObject(JNIEnv *env, jobject javaThis); 50 | static void getCachedMethod(JNIEnv *env, jobject javaThis); 51 | static void getInvalidCachedMethod(JNIEnv *env, jobject javaThis); 52 | static void getCachedMethodOnUninitialized(JNIEnv *env, jobject javaThis); 53 | static void getCachedField(JNIEnv *env, jobject javaThis); 54 | static void getInvalidCachedField(JNIEnv *env, jobject javaThis); 55 | static void getCachedFieldOnUninitialized(JNIEnv *env, jobject javaThis); 56 | static void cacheInvalidField(JNIEnv *env, jobject javaThis); 57 | static void cacheInvalidMethod(JNIEnv *env, jobject javaThis); 58 | }; 59 | 60 | #endif // __JavaClassTest_h__ 61 | -------------------------------------------------------------------------------- /src/main/cpp/ClassRegistry.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "ClassRegistry.h" 23 | 24 | namespace spotify { 25 | namespace jni { 26 | 27 | ClassRegistry::ClassRegistry() { 28 | LOG_DEBUG("Creating new class registry"); 29 | } 30 | 31 | ClassRegistry::~ClassRegistry() { 32 | LOG_DEBUG("Destroying class registry"); 33 | } 34 | 35 | void ClassRegistry::add(JNIEnv *env, const JavaClass *item) { 36 | if (item == NULL) { 37 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 38 | "Can't add null item to registry"); 39 | return; 40 | } else if (item->getCanonicalName() == NULL || strlen(item->getCanonicalName()) == 0) { 41 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 42 | "Can't add item with empty canonical name to registry"); 43 | } else if (!item->isInitialized()) { 44 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 45 | "Can't add uninitialized JavaClass to registry"); 46 | } else { 47 | LOG_INFO("Adding class instance '%s' to registry", item->getCanonicalName()); 48 | _classes[item->getCanonicalName()].set(item); 49 | } 50 | } 51 | 52 | const JavaClass* ClassRegistry::get(const char* name) const { 53 | LOG_DEBUG("Looking up class named '%s'", name); 54 | if (name == NULL) { 55 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 56 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 57 | "Can't call get() with NULL"); 58 | return NULL; 59 | } 60 | 61 | const ClassRegistryMap::const_iterator iter = _classes.find(name); 62 | return iter != _classes.end() ? iter->second.get() : NULL; 63 | } 64 | 65 | } // namespace jni 66 | } // namespace spotify 67 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/ClassRegistryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Test; 25 | 26 | public class ClassRegistryTest { 27 | static { 28 | System.loadLibrary("JniHelpersTest"); 29 | } 30 | 31 | @Test 32 | native public void createRegistry() throws Exception; 33 | 34 | @Test 35 | native public void addClass() throws Exception; 36 | 37 | @Test(expected = IllegalArgumentException.class) 38 | native public void addNullClass() throws Exception; 39 | 40 | @Test(expected = IllegalArgumentException.class) 41 | native public void addClassWithEmptyName() throws Exception; 42 | 43 | @Test(expected = IllegalArgumentException.class) 44 | native public void addClassWithNullName() throws Exception; 45 | 46 | @Test(expected = IllegalArgumentException.class) 47 | native public void addClassWithoutInfo() throws Exception; 48 | 49 | @Test 50 | native public void addClassMultipleTimes() throws Exception; 51 | 52 | @Test 53 | native public void get() throws Exception; 54 | 55 | @Test 56 | native public void getWithBracketOperator() throws Exception; 57 | 58 | @Test(expected = IllegalArgumentException.class) 59 | native public void getNullClass() throws Exception; 60 | 61 | @Test 62 | native public void getInvalidClass() throws Exception; 63 | 64 | native public void nativeNewInstance(TestObject instance); 65 | 66 | @Test 67 | public void newInstance() throws Exception { 68 | TestObject testObject = TestObject.createTestObject(); 69 | nativeNewInstance(testObject); 70 | } 71 | 72 | native public void nativeNewInstanceWithNull(TestObject instance); 73 | 74 | @Test 75 | public void newInstanceWithNull() throws Exception { 76 | nativeNewInstanceWithNull(null); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/JavaClassUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertNotNull; 28 | 29 | public class JavaClassUtilsTest { 30 | static { 31 | System.loadLibrary("JniHelpersTest"); 32 | } 33 | 34 | public native Class findObjectClass(); 35 | 36 | @Test 37 | public void findClass() throws Exception { 38 | Class result = findObjectClass(); 39 | assertNotNull(result); 40 | assertEquals(Object.class.getCanonicalName(), result.getCanonicalName()); 41 | } 42 | 43 | @Test(expected = NoClassDefFoundError.class) 44 | public native void findInvalidClass() throws Exception; 45 | 46 | public native Class nativeFindClassWithLoader(); 47 | 48 | @Test 49 | public void findClassWithLoader() throws Exception { 50 | Class result = nativeFindClassWithLoader(); 51 | assertNotNull(result); 52 | assertEquals(this.getClass().getCanonicalName(), result.getCanonicalName()); 53 | } 54 | 55 | @Test(expected = NoClassDefFoundError.class) 56 | public native void findInvalidClassWithLoader() throws Exception; 57 | 58 | @Test(expected = IllegalArgumentException.class) 59 | public native void makeNameForSignatureWithNull() throws Exception; 60 | 61 | @Test 62 | public native void makeNameForSignatureWithPrimitive() throws Exception; 63 | 64 | @Test 65 | public native void makeNameForSignatureWithArray() throws Exception; 66 | 67 | @Test 68 | public native void makeNameForSignatureWithObject() throws Exception; 69 | 70 | @Test 71 | public native void makeNameForSignatureWithJniSignature() throws Exception; 72 | 73 | @Test 74 | public native void makeNameForSignatureWithArrayOfObjects() throws Exception; 75 | } 76 | -------------------------------------------------------------------------------- /src/test/cpp/ClassRegistryTest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __ClassRegistryTest_h__ 23 | #define __ClassRegistryTest_h__ 24 | 25 | #include "JniHelpersTest.h" 26 | #include "JniHelpers.h" 27 | 28 | class ClassWithName : public JavaClass { 29 | public: 30 | ClassWithName(const char *name) : JavaClass(), _name(name) {} 31 | ClassWithName(JNIEnv *env) : JavaClass(env) { initialize(env); } 32 | ~ClassWithName() {} 33 | 34 | void initialize(JNIEnv *env) {} 35 | void mapFields() {} 36 | const char* getCanonicalName() const { return _name; } 37 | void setJavaObject(JNIEnv *env, jobject javaObject) {} 38 | jobject toJavaObject(JavaClass *nativeObject) { return NULL; } 39 | 40 | private: 41 | const char* _name; 42 | }; 43 | 44 | class EXPORT ClassRegistryTest : public JavaClass { 45 | public: 46 | ClassRegistryTest() : JavaClass() {} 47 | ClassRegistryTest(JNIEnv *env) : JavaClass(env) { initialize(env); } 48 | ~ClassRegistryTest() {} 49 | 50 | void initialize(JNIEnv *env); 51 | void mapFields() {} 52 | const char* getCanonicalName() const { 53 | return MAKE_CANONICAL_NAME(PACKAGE, ClassRegistryTest); 54 | } 55 | 56 | private: 57 | static void createRegistry(JNIEnv *env, jobject javaThis); 58 | 59 | static void addClass(JNIEnv *env, jobject javaThis); 60 | static void addNullClass(JNIEnv *env, jobject javaThis); 61 | static void addClassWithEmptyName(JNIEnv *env, jobject javaThis); 62 | static void addClassWithNullName(JNIEnv *env, jobject javaThis); 63 | static void addClassWithoutInfo(JNIEnv *env, jobject javaThis); 64 | static void addClassMultipleTimes(JNIEnv *env, jobject javaThis); 65 | 66 | static void get(JNIEnv *env, jobject javaThis); 67 | static void getWithBracketOperator(JNIEnv *env, jobject javaThis); 68 | static void getNullClass(JNIEnv *env, jobject javaThis); 69 | static void getInvalidClass(JNIEnv *env, jobject javaThis); 70 | 71 | static void nativeNewInstance(JNIEnv *env, jobject javaThis, jobject javaTestObject); 72 | static void nativeNewInstanceWithNull(JNIEnv *env, jobject javaThis, jobject javaTestObject); 73 | }; 74 | 75 | #endif // __ClassRegistryTest_h__ 76 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/JavaExceptionUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | 28 | public class JavaExceptionUtilsTest { 29 | static { 30 | System.loadLibrary("JniHelpersTest"); 31 | } 32 | 33 | // TODO: Need to test checkException, not sure how 34 | 35 | public native void nativeThrowException() throws Exception; 36 | 37 | @Test(expected = Exception.class) 38 | public void throwException() throws Exception { 39 | try { 40 | nativeThrowException(); 41 | } catch (Exception e) { 42 | assertEquals(TestConstants.TEST_STRING, e.getMessage()); 43 | throw e; 44 | } 45 | } 46 | 47 | public native void nativeThrowExceptionWithFormatString() throws Exception; 48 | 49 | @Test 50 | public void testThrowExceptionWithFormatString() throws Exception { 51 | try { 52 | nativeThrowExceptionWithFormatString(); 53 | } catch (Exception e) { 54 | String expected = TestConstants.TEST_STRING + ", " + TestConstants.TEST_INTEGER; 55 | assertEquals(expected, e.getMessage()); 56 | } 57 | } 58 | 59 | public native void nativeThrowRuntimeException() throws Exception; 60 | 61 | @Test(expected = RuntimeException.class) 62 | public void throwRuntimeException() throws Exception { 63 | try { 64 | nativeThrowRuntimeException(); 65 | } catch (RuntimeException e) { 66 | assertEquals(TestConstants.TEST_STRING, e.getMessage()); 67 | throw e; 68 | } 69 | } 70 | 71 | public native void nativeThrowExceptionOfType() throws Exception; 72 | 73 | @Test(expected = UnsupportedOperationException.class) 74 | public void throwExceptionOfType() throws Exception { 75 | try { 76 | nativeThrowExceptionOfType(); 77 | } catch (UnsupportedOperationException e) { 78 | assertEquals(TestConstants.TEST_STRING, e.getMessage()); 79 | throw e; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/cpp/TestObject.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __TestObject_h__ 23 | #define __TestObject_h__ 24 | 25 | #include "JniHelpers.h" 26 | #include "JniHelpersTest.h" 27 | 28 | using namespace spotify::jni; 29 | 30 | /** 31 | * @brief Basic model-type class 32 | * 33 | * This class is a very simple object which holds a few pieces of data. It 34 | * demonstrates conversion between Java <--> C++ objects. 35 | */ 36 | class EXPORT TestObject : public JavaClass { 37 | public: 38 | /** 39 | * @brief Basic no-arg constructor 40 | * 41 | * If you want to use ClassRegistry::newInstance(), then you must provide 42 | * a no-arg constructor. 43 | */ 44 | TestObject(); 45 | /** 46 | * @brief Initialization constructor 47 | * @param env JNIEnv 48 | * 49 | * Normally you shouldn't call this constructor directly, instead it is invoked 50 | * in JNI_OnLoad and should call initialize() in order to cache any method/field 51 | * definitions for later. 52 | */ 53 | TestObject(JNIEnv *env); 54 | ~TestObject() {} 55 | 56 | /** 57 | * @brief Return the class canonical name 58 | * @return String with JNI canonical name (ie, "com/example/Foo") 59 | * 60 | * Use of the MAKE_CANONICAL_NAME macro is recommended here 61 | */ 62 | const char* getCanonicalName() const { 63 | return MAKE_CANONICAL_NAME(PACKAGE, TestObject); 64 | } 65 | 66 | /** 67 | * @brief Initialize class info, which is utilized by ClassRegistry::newInstance 68 | * @param env JNIEnv 69 | */ 70 | virtual void initialize(JNIEnv *env); 71 | 72 | /** 73 | * @brief Configure field mappings 74 | * 75 | * Field mappings tell JniHelpers how to copy data between Java <--> C++ instances 76 | * when invoking toJavaObject() and setJavaObject(). If you do not need to use 77 | * these methods, then it is not necessary to do any work in this method. 78 | */ 79 | virtual void mapFields(); 80 | 81 | public: 82 | JavaString string; 83 | int i; 84 | short s; 85 | float f; 86 | double d; 87 | bool z; 88 | signed char b; 89 | wchar_t c; 90 | // TODO: byte[] b; 91 | }; 92 | 93 | #endif // __TestObject_h__ 94 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/ByteArrayTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | 28 | public class ByteArrayTest { 29 | static { 30 | System.loadLibrary("JniHelpersTest"); 31 | } 32 | 33 | private byte[] getTestByteArray() { 34 | byte[] data = new byte[10]; 35 | for (int i = 0; i < data.length; i++) { 36 | data[i] = (byte) i; 37 | } 38 | return data; 39 | } 40 | 41 | @Test 42 | public native void createNewByteArray() throws Exception; 43 | 44 | @Test 45 | public native void createNewByteArrayWithData() throws Exception; 46 | 47 | @Test 48 | public native void createNewByteArrayWithDataCopy() throws Exception; 49 | 50 | public native void nativeCreateNewByteArrayWithJavaData(byte[] data); 51 | 52 | @Test 53 | public void createNewByteArrayWithJavaData() throws Exception { 54 | nativeCreateNewByteArrayWithJavaData(getTestByteArray()); 55 | } 56 | 57 | @Test 58 | public native void createNewByteArrayWithNull() throws Exception; 59 | 60 | @Test 61 | public native void createNewByteArrayWithNullAndNonZeroLength() throws Exception; 62 | 63 | public native byte[] nativeGetTestJavaByteArray(); 64 | 65 | @Test 66 | public void getJavaByteArray() throws Exception { 67 | byte[] result = nativeGetTestJavaByteArray(); 68 | byte[] expected = getTestByteArray(); 69 | assertEquals(expected.length, result.length); 70 | for (int i = 0; i < result.length; i++) { 71 | assertEquals(expected[i], result[i]); 72 | } 73 | } 74 | 75 | @Test 76 | public native void setData() throws Exception; 77 | 78 | @Test 79 | public native void setDataWithCopy() throws Exception; 80 | 81 | public native void nativeSetJavaByteArray(byte[] data, int expectedSize); 82 | 83 | @Test 84 | public void setJavaData() throws Exception { 85 | byte[] expected = getTestByteArray(); 86 | nativeSetJavaByteArray(expected, expected.length); 87 | } 88 | 89 | @Test 90 | public void setJavaDataWithNull() throws Exception { 91 | nativeSetJavaByteArray(null, 0); 92 | } 93 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/TestObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | public class TestObject { 25 | String string; 26 | int i; 27 | short s; 28 | float f; 29 | double d; 30 | boolean z; 31 | byte b; 32 | char c; 33 | 34 | public TestObject() { 35 | // TODO: Need to deal with classes that don't have a no-arg ctor 36 | } 37 | 38 | private TestObject(String string, int i, short s, float f, double d, boolean z, byte b, char c) { 39 | this.string = string; 40 | this.i = i; 41 | this.s = s; 42 | this.f = f; 43 | this.d = d; 44 | this.z = z; 45 | this.b = b; 46 | this.c = c; 47 | } 48 | 49 | public static TestObject createTestObject() { 50 | return new TestObject(TestConstants.TEST_STRING, 51 | TestConstants.TEST_INTEGER, 52 | TestConstants.TEST_SHORT, 53 | TestConstants.TEST_FLOAT, 54 | TestConstants.TEST_DOUBLE, 55 | TestConstants.TEST_BOOLEAN, 56 | TestConstants.TEST_BYTE, 57 | TestConstants.TEST_CHAR 58 | ); 59 | } 60 | 61 | public String getString() { 62 | return string; 63 | } 64 | 65 | public void setString(String string) { 66 | this.string = string; 67 | } 68 | 69 | public int getI() { 70 | return i; 71 | } 72 | 73 | public void setI(int i) { 74 | this.i = i; 75 | } 76 | 77 | public short getS() { 78 | return s; 79 | } 80 | 81 | public void setS(short s) { 82 | this.s = s; 83 | } 84 | 85 | public float getF() { 86 | return f; 87 | } 88 | 89 | public void setF(float f) { 90 | this.f = f; 91 | } 92 | 93 | public double getD() { 94 | return d; 95 | } 96 | 97 | public void setD(double d) { 98 | this.d = d; 99 | } 100 | 101 | public boolean getZ() { 102 | return z; 103 | } 104 | 105 | public void setZ(boolean z) { 106 | this.z = z; 107 | } 108 | 109 | public byte getB() { 110 | return b; 111 | } 112 | 113 | public void setB(byte b) { 114 | this.b = b; 115 | } 116 | 117 | public char getC() { 118 | return c; 119 | } 120 | 121 | public void setC(char c) { 122 | this.c = c; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/cpp/JavaThreadUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaThreadUtils.h" 23 | #include "JavaExceptionUtils.h" 24 | 25 | namespace spotify { 26 | namespace jni { 27 | 28 | static JavaVM* sJavaVm = NULL; 29 | 30 | JNIEnv* JavaThreadUtils::initialize(JavaVM *jvm) { 31 | sJavaVm = jvm; 32 | return getEnvForCurrentThread(jvm); 33 | } 34 | 35 | JavaVM* JavaThreadUtils::getJavaVM() { 36 | return sJavaVm; 37 | } 38 | 39 | JNIEnv* JavaThreadUtils::getEnvForCurrentThread() { 40 | return getEnvForCurrentThread(sJavaVm); 41 | } 42 | 43 | JNIEnv* JavaThreadUtils::getEnvForCurrentThread(JavaVM *jvm) { 44 | JNIEnv *env; 45 | 46 | if (jvm == NULL) { 47 | return NULL; 48 | } else if (jvm->GetEnv(reinterpret_cast(&env), JAVA_VERSION) != JNI_OK) { 49 | // The current thread isn't attached to a Java thread, this could happen when 50 | // mistakenly calling JniHelpers functions directly from other C++ code. 51 | return NULL; 52 | } 53 | 54 | return env; 55 | } 56 | 57 | JNIEnv* JavaThreadUtils::attachCurrentThreadToJVM(const char* thread_name) { 58 | JNIEnv *env; 59 | JavaVMAttachArgs args; 60 | int result = -1; 61 | 62 | args.version = JAVA_VERSION; 63 | args.name = const_cast(thread_name); 64 | args.group = NULL; 65 | 66 | #ifdef ANDROID 67 | result = sJavaVm->AttachCurrentThread(&env, &args); 68 | #else 69 | result = sJavaVm->AttachCurrentThread((void**)(&env), &args); 70 | #endif 71 | if (result != JNI_OK) { 72 | JavaExceptionUtils::throwRuntimeException(env, "Could not attach thread %s to JVM", thread_name); 73 | return NULL; 74 | } 75 | 76 | return env; 77 | } 78 | 79 | JNIEnv* JavaThreadUtils::attachCurrentThreadAsDaemonToJVM(const char* thread_name) { 80 | JNIEnv *env; 81 | JavaVMAttachArgs args; 82 | int result = -1; 83 | 84 | args.version = JAVA_VERSION; 85 | args.name = const_cast(thread_name); 86 | args.group = NULL; 87 | 88 | #ifdef ANDROID 89 | result = sJavaVm->AttachCurrentThreadAsDaemon(&env, &args); 90 | #else 91 | result = sJavaVm->AttachCurrentThreadAsDaemon((void**)(&env), &args); 92 | #endif 93 | if (result != JNI_OK) { 94 | JavaExceptionUtils::throwRuntimeException(env, "Could not attach daemon thread %s to JVM", thread_name); 95 | return NULL; 96 | } 97 | 98 | return env; 99 | } 100 | 101 | void JavaThreadUtils::detatchCurrentThreadFromJVM() { 102 | sJavaVm->DetachCurrentThread(); 103 | } 104 | 105 | } // namespace jni 106 | } // namespace spotify 107 | -------------------------------------------------------------------------------- /src/main/cpp/ByteArray.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "ByteArray.h" 23 | #include "JavaExceptionUtils.h" 24 | #include 25 | 26 | namespace spotify { 27 | namespace jni { 28 | 29 | ByteArray::ByteArray() : _data(NULL), _num_bytes(0) {} 30 | 31 | ByteArray::ByteArray(void *data, const size_t numBytes, bool copyData) : 32 | _data(NULL), _num_bytes(0) { 33 | // In the rare (but possible) event that this constructor is called with 34 | // NULL but non-zero length data, correct the byte count so as to avoid 35 | // segfaults later on. 36 | if (data == NULL && numBytes > 0) { 37 | _num_bytes = 0; 38 | } else if (data != NULL && numBytes > 0) { 39 | set(data, numBytes, copyData); 40 | } 41 | } 42 | 43 | ByteArray::ByteArray(JNIEnv *env, jbyteArray data) : 44 | _data(NULL), _num_bytes(0) { 45 | set(env, data); 46 | } 47 | 48 | ByteArray::~ByteArray() { 49 | if (_data != NULL) { 50 | free(_data); 51 | _data = NULL; 52 | } 53 | } 54 | 55 | void *ByteArray::leak() { 56 | void *result = _data; 57 | _data = NULL; 58 | _num_bytes = 0; 59 | return result; 60 | } 61 | 62 | JniLocalRef ByteArray::toJavaByteArray(JNIEnv *env) const { 63 | JniLocalRef result = env->NewByteArray((jsize)_num_bytes); 64 | JavaExceptionUtils::checkException(env); 65 | if (_num_bytes == 0 || _data == NULL) { 66 | return result; 67 | } 68 | env->SetByteArrayRegion(result, 0, (jsize)_num_bytes, (jbyte *)_data); 69 | return result.leak(); 70 | } 71 | 72 | void ByteArray::set(void *data, const size_t numBytes, bool copyData) { 73 | if (data == NULL && numBytes > 0) { 74 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 75 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 76 | "Cannot set data with non-zero size and NULL object"); 77 | return; 78 | } 79 | 80 | // Make sure not to leak the old data if it exists 81 | if (_data != NULL) { 82 | free(_data); 83 | } 84 | 85 | if (copyData) { 86 | _data = malloc(numBytes); 87 | memcpy(_data, data, numBytes); 88 | } else { 89 | _data = data; 90 | } 91 | _num_bytes = numBytes; 92 | } 93 | 94 | void ByteArray::set(JNIEnv *env, jbyteArray data) { 95 | if (_data != NULL) { 96 | free(_data); 97 | } 98 | 99 | if (data != NULL) { 100 | _num_bytes = env->GetArrayLength(data); 101 | if (_num_bytes == 0) { 102 | _data = NULL; 103 | } else { 104 | _data = malloc(_num_bytes); 105 | env->GetByteArrayRegion(data, 0, (jsize)_num_bytes, (jbyte *)_data); 106 | } 107 | } 108 | } 109 | 110 | } // namespace jni 111 | } // namespace spotify 112 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/JavaClassTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | 28 | public class JavaClassTest { 29 | static { 30 | System.loadLibrary("JniHelpersTest"); 31 | } 32 | 33 | @Test 34 | native public void createJavaClass() throws Exception; 35 | 36 | @Test 37 | native public void isInitialized() throws Exception; 38 | 39 | @Test 40 | native public void getCanonicalName() throws Exception; 41 | 42 | @Test 43 | native public void getSimpleName() throws Exception; 44 | 45 | @Test 46 | native public void merge() throws Exception; 47 | 48 | native public void nativeSetJavaObject(TestObject object); 49 | 50 | @Test 51 | public void setJavaObject() throws Exception { 52 | TestObject object = TestObject.createTestObject(); 53 | nativeSetJavaObject(object); 54 | } 55 | 56 | native public TestObject nativeToJavaObject(); 57 | 58 | @Test 59 | public void toJavaObject() throws Exception { 60 | TestObject object = nativeToJavaObject(); 61 | assertEquals(TestConstants.TEST_STRING, object.string); 62 | assertEquals(TestConstants.TEST_INTEGER, object.i); 63 | assertEquals(TestConstants.TEST_SHORT, object.s); 64 | assertEquals(TestConstants.TEST_FLOAT, object.f, TestConstants.TEST_FLOAT_TOLERANCE); 65 | assertEquals(TestConstants.TEST_DOUBLE, object.d, TestConstants.TEST_FLOAT_TOLERANCE); 66 | assertEquals(TestConstants.TEST_BOOLEAN, object.z); 67 | assertEquals(TestConstants.TEST_BYTE, object.b); 68 | 69 | if (JavaStringTest.supportsRawStringLiterals()) { 70 | assertEquals(TestConstants.TEST_CHAR, object.c); 71 | } 72 | } 73 | 74 | @Test 75 | native public void getCachedMethod() throws Exception; 76 | 77 | @Test(expected = IllegalArgumentException.class) 78 | native public void getInvalidCachedMethod() throws Exception; 79 | 80 | @Test(expected = IllegalStateException.class) 81 | native public void getCachedMethodOnUninitialized() throws Exception; 82 | 83 | @Test 84 | native public void getCachedField() throws Exception; 85 | 86 | @Test(expected = IllegalArgumentException.class) 87 | native public void getInvalidCachedField() throws Exception; 88 | 89 | @Test(expected = IllegalStateException.class) 90 | native public void getCachedFieldOnUninitialized() throws Exception; 91 | 92 | @Test(expected = NoSuchFieldError.class) 93 | native public void cacheInvalidField() throws Exception; 94 | 95 | @Test(expected = NoSuchMethodError.class) 96 | native public void cacheInvalidMethod() throws Exception; 97 | } 98 | -------------------------------------------------------------------------------- /src/main/cpp/ShortArray.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "ShortArray.h" 23 | #include "JavaExceptionUtils.h" 24 | #include 25 | 26 | namespace spotify { 27 | namespace jni { 28 | 29 | ShortArray::ShortArray() : _data(NULL), _num_elements(0) {} 30 | 31 | ShortArray::ShortArray(short *data, const size_t numElements, bool copyData) : 32 | _data(NULL), _num_elements(0) { 33 | // In the rare (but possible) event that this constructor is called with 34 | // NULL but non-zero length data, correct the byte count so as to avoid 35 | // segfaults later on. 36 | if (data == NULL && numElements > 0) { 37 | _num_elements = 0; 38 | } else if (data != NULL && numElements > 0) { 39 | set(data, numElements, copyData); 40 | } 41 | } 42 | 43 | ShortArray::ShortArray(JNIEnv *env, jshortArray data) : 44 | _data(NULL), _num_elements(0) { 45 | set(env, data); 46 | } 47 | 48 | ShortArray::~ShortArray() { 49 | if (_data != NULL) { 50 | free(_data); 51 | _data = NULL; 52 | } 53 | } 54 | 55 | short *ShortArray::leak() { 56 | short *result = _data; 57 | _data = NULL; 58 | _num_elements = 0; 59 | return result; 60 | } 61 | 62 | JniLocalRef ShortArray::toJavaShortArray(JNIEnv *env) const { 63 | JniLocalRef result = env->NewShortArray((jsize)_num_elements); 64 | JavaExceptionUtils::checkException(env); 65 | if (_num_elements == 0 || _data == NULL) { 66 | return result; 67 | } 68 | env->SetShortArrayRegion(result, 0, (jsize)_num_elements, (jshort *)_data); 69 | return result.leak(); 70 | } 71 | 72 | void ShortArray::set(short *data, const size_t numElements, bool copyData) { 73 | if (data == NULL && numElements > 0) { 74 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 75 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 76 | "Cannot set data with non-zero size and NULL object"); 77 | return; 78 | } 79 | 80 | // Make sure not to leak the old data if it exists 81 | if (_data != NULL) { 82 | free(_data); 83 | } 84 | 85 | if (copyData) { 86 | _data = (short *)malloc(numElements); 87 | memcpy(_data, data, numElements); 88 | } else { 89 | _data = data; 90 | } 91 | _num_elements = numElements; 92 | } 93 | 94 | void ShortArray::set(JNIEnv *env, jshortArray data) { 95 | if (_data != NULL) { 96 | free(_data); 97 | } 98 | 99 | if (data != NULL) { 100 | _num_elements = env->GetArrayLength(data); 101 | if (_num_elements == 0) { 102 | _data = NULL; 103 | } else { 104 | _data = (short *)malloc(_num_elements); 105 | env->GetShortArrayRegion(data, 0, (jsize)_num_elements, (jshort *)_data); 106 | } 107 | } 108 | } 109 | 110 | } // namespace jni 111 | } // namespace spotify 112 | -------------------------------------------------------------------------------- /src/test/cpp/TestObject.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "TestObject.h" 23 | 24 | TestObject::TestObject() : JavaClass(), 25 | string(), i(0), s(0), f(0.0f), d(0.0), z(false), b(0), c(0) { 26 | } 27 | 28 | TestObject::TestObject(JNIEnv *env) : JavaClass(env), 29 | string(), i(0), s(0), f(0.0f), d(0.0), z(false), b(0), c(0) { 30 | initialize(env); 31 | // Set up field mappings for the instance. Normally this isn't necessary, 32 | // since the global instance is typically used for merging into other objects. 33 | // If you want to actually use the initialized instance in combination with 34 | // setJavaObject(), you should call this though. 35 | merge(this); 36 | } 37 | 38 | void TestObject::initialize(JNIEnv *env) { 39 | setClass(env); 40 | 41 | // Cache some jfieldIDs for quick lookup later. Note that this is necessary 42 | // for the corresponding fields to be mapped in mapFields(). 43 | cacheField(env, "string", kTypeString); 44 | cacheField(env, "i", kTypeInt); 45 | cacheField(env, "s", kTypeShort); 46 | cacheField(env, "f", kTypeFloat); 47 | cacheField(env, "d", kTypeDouble); 48 | cacheField(env, "z", kTypeBool); 49 | cacheField(env, "b", kTypeByte); 50 | cacheField(env, "c", kTypeChar); 51 | // cacheField(env, "b", kTypeArray(kTypeByte)); 52 | 53 | // Cache default constructor 54 | cacheConstructor(env); 55 | 56 | // Cache methods for later. Currently these are unused, but would provide an 57 | // easy mechanism for calling Java methods. Note that when calling cacheMethod, 58 | // the last argument must always be NULL. See the documentation for 59 | // JavaClassUtils::makeSignature for more details. 60 | cacheMethod(env, "getString", kTypeString, NULL); 61 | cacheMethod(env, "setString", kTypeVoid, kTypeString, NULL); 62 | cacheMethod(env, "getI", kTypeInt, NULL); 63 | cacheMethod(env, "setI", kTypeVoid, kTypeInt, NULL); 64 | cacheMethod(env, "getS", kTypeShort, NULL); 65 | cacheMethod(env, "setS", kTypeVoid, kTypeShort, NULL); 66 | cacheMethod(env, "getF", kTypeFloat, NULL); 67 | cacheMethod(env, "setF", kTypeVoid, kTypeFloat, NULL); 68 | cacheMethod(env, "getD", kTypeDouble, NULL); 69 | cacheMethod(env, "setD", kTypeVoid, kTypeDouble, NULL); 70 | cacheMethod(env, "getZ", kTypeBool, NULL); 71 | cacheMethod(env, "setZ", kTypeVoid, kTypeBool, NULL); 72 | cacheMethod(env, "getB", kTypeByte, NULL); 73 | cacheMethod(env, "setB", kTypeVoid, kTypeByte, NULL); 74 | cacheMethod(env, "getC", kTypeChar, NULL); 75 | cacheMethod(env, "setC", kTypeVoid, kTypeChar, NULL); 76 | // TODO: Getters/setters for byte array 77 | } 78 | 79 | void TestObject::mapFields() { 80 | mapField("i", kTypeInt, &i); 81 | mapField("s", kTypeShort, &s); 82 | mapField("f", kTypeFloat, &f); 83 | mapField("d", kTypeDouble, &d); 84 | mapField("string", kTypeString, &string); 85 | mapField("z", kTypeBool, &z); 86 | mapField("b", kTypeByte, &b); 87 | mapField("c", kTypeChar, &c); 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/JavaStringTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Ignore; 25 | import org.junit.Test; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertTrue; 29 | 30 | public class JavaStringTest { 31 | static { 32 | System.loadLibrary("JniHelpersTest"); 33 | } 34 | 35 | native static public boolean supportsRawStringLiterals(); 36 | 37 | @Test 38 | native public void createJavaString() throws Exception; 39 | 40 | native public void nativeCreateJavaStringFromJavaString(String s); 41 | @Test 42 | native public void createJavaStringFromStdString() throws Exception; 43 | 44 | @Test 45 | public void createJavaStringFromJavaString() throws Exception { 46 | nativeCreateJavaStringFromJavaString(TestConstants.TEST_STRING); 47 | } 48 | 49 | native public String nativeGetJavaString(); 50 | @Test 51 | public void getJavaString() throws Exception { 52 | String result = nativeGetJavaString(); 53 | assertEquals(TestConstants.TEST_STRING, result); 54 | } 55 | 56 | native public String nativeGetJavaStringWithNullChar(); 57 | @Test 58 | public void getJavaStringTruncatesAtNullChar() throws Exception { 59 | assertTrue(TestConstants.TEST_STRING_WITH_NULL_CHAR.contains(TestConstants.TEST_NULL_CHAR)); 60 | String result = nativeGetJavaStringWithNullChar(); 61 | String expected = TestConstants.TEST_STRING_WITH_NULL_CHAR.substring(0, 62 | TestConstants.TEST_STRING_WITH_NULL_CHAR.indexOf(TestConstants.TEST_NULL_CHAR)); 63 | assertEquals(expected, result); 64 | } 65 | 66 | native public String nativeGetJavaStringUtf16(); 67 | @Ignore("Not currently supported") 68 | @Test 69 | public void getJavaStringUtf16() throws Exception { 70 | String result = nativeGetJavaStringUtf16(); 71 | assertEquals(TestConstants.TEST_UTF16_STRING, result); 72 | } 73 | 74 | native public String nativeGetJavaStringUtf8(); 75 | @Test 76 | public void getJavaStringUtf8() throws Exception { 77 | if (JavaStringTest.supportsRawStringLiterals()) { 78 | String result = nativeGetJavaStringUtf8(); 79 | assertEquals(TestConstants.TEST_UTF8_STRING, result); 80 | } 81 | } 82 | 83 | native public void nativeSetValue(String s); 84 | @Test 85 | public void setValue() throws Exception { 86 | nativeSetValue(TestConstants.TEST_STRING); 87 | } 88 | 89 | @Test 90 | native public void nativeSetValueWithOperator() throws Exception; 91 | 92 | native public String nativeSetAndReturnValue(String s); 93 | @Test 94 | public void setValueWithNullChar() throws Exception { 95 | String result = nativeSetAndReturnValue(TestConstants.TEST_STRING_WITH_NULL_CHAR); 96 | assertEquals(TestConstants.TEST_STRING_WITH_NULL_CHAR, result); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/cpp/JniTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniTypes_h__ 23 | #define __JniTypes_h__ 24 | 25 | #include 26 | 27 | /** @brief Java int primitive */ 28 | #define kTypeInt "I" 29 | /** @brief Java short primitive */ 30 | #define kTypeShort "S" 31 | /** @brief Java long primitive */ 32 | #define kTypeLong "J" 33 | /** @brief Java float primitive */ 34 | #define kTypeFloat "F" 35 | /** @brief Java double primitive */ 36 | #define kTypeDouble "D" 37 | /** @brief Java bool primitive */ 38 | #define kTypeBool "Z" 39 | /** @brief Java byte primitive */ 40 | #define kTypeByte "B" 41 | /** @brief Java char primitive */ 42 | #define kTypeChar "C" 43 | /** @brief Java void */ 44 | #define kTypeVoid "V" 45 | /** @brief Java String class */ 46 | #define kTypeString "java/lang/String" 47 | /** @brief Java base Object class */ 48 | #define kTypeObject "java/lang/Object" 49 | 50 | /** 51 | * @brief Java class builder macro 52 | * 53 | * It is not necessary to call this macro with a quoted string. For example: 54 | * 55 | * kTypeJavaClass(Object) 56 | * 57 | * Will produce the string "java/lang/Object". 58 | */ 59 | #define kTypeJavaClass(x) "java/lang/" #x 60 | 61 | /** @brief Java base exception */ 62 | #define kTypeException kTypeJavaClass(Exception) 63 | /** @brief Java illegal argument exception */ 64 | #define kTypeIllegalArgumentException kTypeJavaClass(IllegalArgumentException) 65 | /** @brief Java illegal state exception */ 66 | #define kTypeIllegalStateException kTypeJavaClass(IllegalStateException) 67 | /** @brief Java unsuported operation exception */ 68 | #define kTypeUnsupportedOperationException kTypeJavaClass(UnsupportedOperationException) 69 | 70 | /** 71 | * @brief Java array builder macro for primitives 72 | * 73 | * Use this macro to make an array of primitives, for example: 74 | * 75 | * kTypeArray(kTypeInt) 76 | * 77 | * Will produce "[I", which corresponds to int[] in Java. Do not use this 78 | * macro for non-primitive (ie, Object) arrays, instead use kTypeObjectArray 79 | * for that. 80 | */ 81 | #define kTypeArray(x) "[" x 82 | 83 | /** 84 | * @brief Java array builder macro for objects 85 | * 86 | * Use this macro to make an array of objects, for example: 87 | * 88 | * #define PACKAGE "com/example" 89 | * kTypeArray(MAKE_CANONICAL_NAME(PACKAGE, MyClass)) 90 | * 91 | * Will produce "[Lcom/example/MyClass;", which corresponds to MyClass[] in 92 | * Java. Do not use this macro for arrays with primitive types, instead use 93 | * kTypeArray for that. 94 | * 95 | * Due to the limitations of the preprocessor, it is not possible to call this 96 | * macro with a char* string, instead it must be a string object visible to the 97 | * preprocessor itself. For this reason, use of MAKE_CANONICAL_NAME is advised. 98 | */ 99 | #define kTypeObjectArray(x) "[L" x ";" 100 | 101 | /** 102 | * @brief Compare two Java types for equality 103 | */ 104 | #define TYPE_EQUALS(_TYPE1, _TYPE2) (strcmp(_TYPE1, _TYPE2) == 0) 105 | 106 | #endif // __JniTypes_h__ 107 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/jni/NativeObjectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | package com.spotify.jni; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertNotEquals; 28 | 29 | public class NativeObjectTest { 30 | static { 31 | System.loadLibrary("JniHelpersTest"); 32 | } 33 | 34 | native public PersistedObject createPersistedObject(); 35 | 36 | native public PersistedObject getPersistedInstance(PersistedObject object); 37 | 38 | native public void destroyPersistedObject(PersistedObject object); 39 | 40 | @Test 41 | public void persist() throws Exception { 42 | PersistedObject object = createPersistedObject(); 43 | assertNotEquals(0, object.nPtr); 44 | // These properties should be set by the first native method in this case 45 | assertEquals(TestConstants.TEST_INTEGER, object.i); 46 | 47 | // Now create a new empty object, but copy the nPtr field to it. Note that 48 | // the i field is *not* copied; that value is stored natively and should 49 | // be retrieved in the call to getPersistedInstance. 50 | PersistedObject emptyInstance = new PersistedObject(); 51 | emptyInstance.nPtr = object.nPtr; 52 | 53 | // The native test should be able to fetch the previous instance via nPtr, 54 | // and return to us the same instance data in a new object. 55 | PersistedObject result = getPersistedInstance(emptyInstance); 56 | assertEquals(object.nPtr, result.nPtr); 57 | assertEquals(object.i, result.i); 58 | 59 | // Always clean up after yourself, kids! 60 | destroyPersistedObject(object); 61 | } 62 | 63 | @Test 64 | native public void persistInvalidClass() throws Exception; 65 | 66 | @Test(expected = IllegalArgumentException.class) 67 | native public void persistNullObject() throws Exception; 68 | 69 | @Test 70 | public void destroyPersistedObject() throws Exception { 71 | PersistedObject object = createPersistedObject(); 72 | assertNotEquals(0, object.nPtr); 73 | assertEquals(TestConstants.TEST_INTEGER, object.i); 74 | 75 | destroyPersistedObject(object); 76 | 77 | assertEquals(0, object.nPtr); 78 | // Destroy should only alter the nPtr field, this should remain untouched 79 | assertEquals(TestConstants.TEST_INTEGER, object.i); 80 | } 81 | 82 | @Test 83 | native public void nativeIsPersistenceEnabled() throws Exception; 84 | 85 | @Test 86 | native public void isPersistenceEnabledWithoutInit() throws Exception; 87 | 88 | @Test 89 | native public void destroyInvalidClass() throws Exception; 90 | 91 | @Test(expected = IllegalArgumentException.class) 92 | native public void destroyNullObject() throws Exception; 93 | 94 | @Test 95 | public void destroyFromJava() throws Exception { 96 | PersistedObject object = createPersistedObject(); 97 | assertNotEquals(0, object.nPtr); 98 | object.destroy(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/cpp/JniHelpersCommon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JniHelpersCommon_h__ 23 | #define __JniHelpersCommon_h__ 24 | 25 | #include "JniTypes.h" 26 | #include 27 | 28 | // Disable some annoying compiler warnings 29 | #if WIN32 30 | #pragma warning(disable: 4996) // "Security" warnings for vsnprintf 31 | #endif 32 | 33 | #if WIN32 34 | #define EXPORT __declspec(dllexport) 35 | #else 36 | #define EXPORT 37 | #endif 38 | 39 | // What version of Java should be used when initializing JniHelpers. By default this 40 | // is defined to be Java 1.6. 41 | #ifndef JAVA_VERSION 42 | #define JAVA_VERSION JNI_VERSION_1_6 43 | #endif 44 | 45 | // Whether string literals are supported by the platform. This is a C++11 feature 46 | // which the Visual C++ does not yet support. Currently this feature is only used 47 | // by the test suite. 48 | #if ! WIN32 49 | #define HAS_RAW_STRING_LITERALS 1 50 | #endif 51 | 52 | // Whether the JVM should be forcibly terminated if an exception is thrown. This does 53 | // not apply to exceptions thrown from within JniHelpers or within Java code attached 54 | // to JNI code. This is mostly relevant when looking up classes/methods/fields which 55 | // do not exist, and thus represent a misconfiguration on the programmer's end. In such 56 | // cases it is sometimes prudent to forcibly quit rather than try to continue running. 57 | #ifndef TERMINATE_ON_EXCEPTION 58 | #define TERMINATE_ON_EXCEPTION 0 59 | #endif 60 | 61 | // Windows uses _DEBUG by default, but we prefer plain 'ol DEBUG instead 62 | #if WIN32 63 | #define DEBUG _DEBUG 64 | #endif 65 | 66 | // Determines whether exceptions will be raised by JniHelpers 67 | #ifndef ENABLE_EXCEPTIONS 68 | #define ENABLE_EXCEPTIONS 1 69 | #endif 70 | 71 | // Determines whether logging messages will be printed by JniHelpers 72 | #ifndef ENABLE_LOGGING 73 | #define ENABLE_LOGGING 0 74 | #endif 75 | 76 | // Determines whether debug/info logging messages will be printed by JniHelpers 77 | #ifndef ENABLE_LOGGING_DEBUG 78 | #define ENABLE_LOGGING_DEBUG DEBUG 79 | #endif 80 | 81 | #if ENABLE_LOGGING 82 | #if ANDROID 83 | #include 84 | #define LOGGING_TAG "JniHelpers" 85 | #if ENABLE_LOGGING_DEBUG 86 | #define LOG_DEBUG(...) __android_log_print(ANDROID_LOG_INFO, LOGGING_TAG, __VA_ARGS__) 87 | #define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, LOGGING_TAG, __VA_ARGS__) 88 | #else 89 | #define LOG_DEBUG(...) 90 | #define LOG_INFO(...) 91 | #endif 92 | #define LOG_WARN(...) __android_log_print(ANDROID_LOG_WARN, LOGGING_TAG, __VA_ARGS__) 93 | #define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, LOGGING_TAG, __VA_ARGS__) 94 | #else 95 | #if ENABLE_LOGGING_DEBUG 96 | #define LOG_DEBUG(...) fprintf(stderr, "DEBUG: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") 97 | #define LOG_INFO(...) fprintf(stderr, "INFO: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") 98 | #else 99 | #define LOG_DEBUG(...) 100 | #define LOG_INFO(...) 101 | #endif 102 | #define LOG_WARN(...) fprintf(stderr, "WARN: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") 103 | #define LOG_ERROR(...) fprintf(stderr, "ERROR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") 104 | #endif 105 | #else 106 | #define LOG_DEBUG(...) 107 | #define LOG_INFO(...) 108 | #define LOG_WARN(...) 109 | #define LOG_ERROR(...) 110 | #endif 111 | 112 | #endif // __JniHelpersCommon_h__ 113 | -------------------------------------------------------------------------------- /src/main/cpp/JavaStringArray.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaStringArray.h" 23 | #include "JavaExceptionUtils.h" 24 | #include "JavaString.h" 25 | #include 26 | 27 | namespace spotify { 28 | namespace jni { 29 | 30 | JavaStringArray::JavaStringArray() : _data(NULL), _num_elements(0) {} 31 | 32 | JavaStringArray::JavaStringArray(JavaString **data, const size_t numElements, bool copyData) : 33 | _data(NULL), _num_elements(0) { 34 | // In the rare (but possible) event that this constructor is called with 35 | // NULL but non-zero length data, correct the elements count so as to avoid 36 | // segfaults later on. 37 | if (data == NULL && numElements > 0) { 38 | _num_elements = 0; 39 | } else if (data != NULL && numElements > 0) { 40 | set(data, numElements, copyData); 41 | } 42 | } 43 | 44 | JavaStringArray::JavaStringArray(JNIEnv *env, jobjectArray data) : 45 | _data(NULL), _num_elements(0) { 46 | set(env, data); 47 | } 48 | 49 | JavaStringArray::~JavaStringArray() { 50 | freeData(); 51 | } 52 | 53 | JavaString **JavaStringArray::leak() { 54 | JavaString **result = _data; 55 | _data = NULL; 56 | _num_elements = 0; 57 | return result; 58 | } 59 | 60 | JniLocalRef JavaStringArray::toJavaStringArray(JNIEnv *env) const { 61 | jclass stringClass = env->FindClass(kTypeString); 62 | JniLocalRef result = env->NewObjectArray((jsize)_num_elements, stringClass, 0); 63 | JavaExceptionUtils::checkException(env); 64 | if (_num_elements == 0 || _data == NULL) { 65 | return result; 66 | } 67 | for(size_t i = 0; i < _num_elements; i++) { 68 | // This might be a potential problem as it's not entirely 69 | // certain who owns these. 70 | env->SetObjectArrayElement(result.get(), i, _data[i]->toJavaString(env)); 71 | } 72 | return result.leak(); 73 | } 74 | 75 | void JavaStringArray::set(JavaString **data, const size_t numElements, bool copyData) { 76 | if (data == NULL && numElements > 0) { 77 | JNIEnv *env = JavaThreadUtils::getEnvForCurrentThread(); 78 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 79 | "Cannot set data with non-zero size and NULL object"); 80 | return; 81 | } 82 | 83 | // Make sure not to leak the old data if it exists 84 | freeData(); 85 | 86 | if (copyData) { 87 | _data = (JavaString **)malloc(numElements * sizeof(JavaString *)); 88 | for (size_t i = 0; i < numElements; i++) { 89 | _data[i] = data[i]; 90 | } 91 | } else { 92 | _data = data; 93 | } 94 | _num_elements = numElements; 95 | } 96 | 97 | void JavaStringArray::set(JNIEnv *env, jobjectArray data) { 98 | freeData(); 99 | 100 | if (data != NULL) { 101 | _num_elements = env->GetArrayLength(data); 102 | if (_num_elements == 0) { 103 | _data = NULL; 104 | } else { 105 | _data = (JavaString **)malloc(_num_elements * sizeof(JavaString *)); 106 | for (size_t i = 0; i < _num_elements; i++) { 107 | _data[i] = new JavaString(env, (jstring) env->GetObjectArrayElement(data, i)); 108 | } 109 | } 110 | } 111 | } 112 | 113 | void JavaStringArray::freeData() { 114 | if (_data != NULL) { 115 | for (size_t i = 0; i < size(); i++) { 116 | free(_data[i]); 117 | } 118 | free(_data); 119 | _data = NULL; 120 | } 121 | } 122 | 123 | } // namespace jni 124 | } // namespace spotify 125 | -------------------------------------------------------------------------------- /src/main/cpp/ByteArray.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __ByteArray_h__ 23 | #define __ByteArray_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JniLocalRef.h" 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | /** 32 | * @brief Class for holding Java byte[] data 33 | * 34 | * This class is used for passing raw data arrays through JNI. Internally 35 | * the data is stored as a void*. 36 | */ 37 | class EXPORT ByteArray { 38 | public: 39 | /** 40 | * @brief Create a new empty byte array 41 | */ 42 | ByteArray(); 43 | 44 | /** 45 | * @brief Create a new byte array with data. 46 | * @param data Pointer to data 47 | * @param numBytes Size of data pointed to 48 | * @param copyData True if the data should be copied to this instance 49 | * 50 | * See the documentation for set() regarding the ownership of data stored 51 | * in ByteArray instances. 52 | */ 53 | ByteArray(void *data, const size_t numBytes, bool copyData); 54 | 55 | /** 56 | * @brief Create a new byte array with data from a Java byte[] object 57 | * @param env JNIEnv 58 | * @param data Java byte[] data 59 | */ 60 | ByteArray(JNIEnv *env, jbyteArray data); 61 | 62 | virtual ~ByteArray(); 63 | 64 | /** 65 | * @brief Get a pointer to the natively stored data 66 | */ 67 | const void* get() const { return _data; } 68 | 69 | /** 70 | * @brief Convert data to a Java byte[] array 71 | */ 72 | JniLocalRef toJavaByteArray(JNIEnv *env) const; 73 | 74 | /** 75 | * @brief Return a pointer to the data stored by this instance 76 | * 77 | * When an instance of ByteArray is destroyed, it will attempt to free 78 | * the data stored internally. If this data is still needed elsewhere, 79 | * then you should call leak() or else it will be unavailable. 80 | */ 81 | void *leak(); 82 | 83 | /** 84 | * @brief Store data in this instance 85 | * @param data Pointer to data 86 | * @param numBytes Size of data pointed to 87 | * @param copyData True if the data should be copied to this instance 88 | * 89 | * If copyData is true, then this ByteArray instance owns that data and 90 | * the original data passed to this method can be freed without worry. 91 | * However, if copyData is false, then this ByteArray effectively just 92 | * points to that instance instead. In either case, after setting data 93 | * in a ByteArray, it effectively owns that data. 94 | * 95 | * When this ByteArray is destroyed, it will free the data stored in it, 96 | * regardless of how it has been set. This means that if copyData was 97 | * false and that data is still needed elsewhere, then a segfault will 98 | * probably occur when attempting to access that data after this object 99 | * has been destroyed. Thus, the leak() method can be used to remedy the 100 | * situation by removing the pointer to the data so the ByteArray will 101 | * not free it upon destruction. 102 | * 103 | * It is obviously more efficient to not copy the data, however this can 104 | * cause problems if your code does not respect the ownership behaviors 105 | * described above. 106 | */ 107 | void set(void *data, const size_t numBytes, bool copyData); 108 | 109 | /** 110 | * @brief Store data in this instance from a Java byte[] array 111 | * @param env JNIenv 112 | * @param data Java byte[] array 113 | */ 114 | void set(JNIEnv *env, jbyteArray data); 115 | 116 | /** 117 | * @brief Get the size of the data stored by this instance 118 | */ 119 | const size_t size() const { return _num_bytes; } 120 | 121 | private: 122 | void *_data; 123 | size_t _num_bytes; 124 | }; 125 | 126 | } // namespace jni 127 | } // namespace spotify 128 | 129 | #endif // __ByteArray_h__ 130 | -------------------------------------------------------------------------------- /src/main/cpp/ShortArray.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __ShortArray_h__ 23 | #define __ShortArray_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JniLocalRef.h" 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | /** 32 | * @brief Class for holding Java byte[] data 33 | * 34 | * This class is used for passing raw data arrays through JNI. Internally 35 | * the data is stored as a void*. 36 | */ 37 | class EXPORT ShortArray { 38 | public: 39 | /** 40 | * @brief Create a new empty byte array 41 | */ 42 | ShortArray(); 43 | 44 | /** 45 | * @brief Create a new byte array with data. 46 | * @param data Pointer to data 47 | * @param numElements Number of elements in the input data 48 | * @param copyData True if the data should be copied to this instance 49 | * 50 | * See the documentation for set() regarding the ownership of data stored 51 | * in ShortArray instances. 52 | */ 53 | ShortArray(short *data, const size_t numElements, bool copyData); 54 | 55 | /** 56 | * @brief Create a new byte array with data from a Java byte[] object 57 | * @param env JNIEnv 58 | * @param data Java byte[] data 59 | */ 60 | ShortArray(JNIEnv *env, jshortArray data); 61 | 62 | virtual ~ShortArray(); 63 | 64 | /** 65 | * @brief Get a pointer to the natively stored data 66 | */ 67 | const void* get() const { return _data; } 68 | 69 | /** 70 | * @brief Convert data to a Java byte[] array 71 | */ 72 | JniLocalRef toJavaShortArray(JNIEnv *env) const; 73 | 74 | /** 75 | * @brief Return a pointer to the data stored by this instance 76 | * 77 | * When an instance of ShortArray is destroyed, it will attempt to free 78 | * the data stored internally. If this data is still needed elsewhere, 79 | * then you should call leak() or else it will be unavailable. 80 | */ 81 | short *leak(); 82 | 83 | /** 84 | * @brief Store data in this instance 85 | * @param data Pointer to data 86 | * @param numElements Number of elements in the input data 87 | * @param copyData True if the data should be copied to this instance 88 | * 89 | * If copyData is true, then this ShortArray instance owns that data and 90 | * the original data passed to this method can be freed without worry. 91 | * However, if copyData is false, then this ShortArray effectively just 92 | * points to that instance instead. In either case, after setting data 93 | * in a ShortArray, it effectively owns that data. 94 | * 95 | * When this ShortArray is destroyed, it will free the data stored in it, 96 | * regardless of how it has been set. This means that if copyData was 97 | * false and that data is still needed elsewhere, then a segfault will 98 | * probably occur when attempting to access that data after this object 99 | * has been destroyed. Thus, the leak() method can be used to remedy the 100 | * situation by removing the pointer to the data so the ShortArray will 101 | * not free it upon destruction. 102 | * 103 | * It is obviously more efficient to not copy the data, however this can 104 | * cause problems if your code does not respect the ownership behaviors 105 | * described above. 106 | */ 107 | void set(short *data, const size_t numElements, bool copyData); 108 | 109 | /** 110 | * @brief Store data in this instance from a Java byte[] array 111 | * @param env JNIenv 112 | * @param data Java byte[] array 113 | */ 114 | void set(JNIEnv *env, jshortArray data); 115 | 116 | /** 117 | * @brief Get the number of elements stored in this array 118 | */ 119 | const size_t size() const { return _num_elements; } 120 | 121 | private: 122 | short *_data; 123 | size_t _num_elements; 124 | }; 125 | 126 | } // namespace jni 127 | } // namespace spotify 128 | 129 | #endif // __ShortArray_h__ 130 | -------------------------------------------------------------------------------- /src/main/cpp/JavaExceptionUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaExceptionUtils.h" 23 | #include "JavaClassUtils.h" 24 | #include "JavaString.h" 25 | #include 26 | #include 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | static const size_t kExceptionMaxLength = 512; 32 | 33 | void JavaExceptionUtils::checkException(JNIEnv *env) { 34 | if (env->ExceptionCheck()) { 35 | env->ExceptionDescribe(); 36 | #if TERMINATE_ON_EXCEPTION 37 | std::terminate(); 38 | #endif 39 | } 40 | } 41 | 42 | void JavaExceptionUtils::checkExceptionAndClear(JNIEnv *env) { 43 | checkException(env); 44 | env->ExceptionClear(); 45 | } 46 | 47 | JniLocalRef JavaExceptionUtils::newThrowable(JNIEnv *env, const char *message, ...) { 48 | // Find the Throwable class and its associated String constructor 49 | jclass throwableClazz = JavaClassUtils::findClass(env, kTypeJavaClass(Throwable), false); 50 | if (throwableClazz == NULL) { 51 | JavaExceptionUtils::throwRuntimeException(env, "Could not find class Throwable"); 52 | return NULL; 53 | } 54 | 55 | std::string signature; 56 | JavaClassUtils::makeSignature(signature, kTypeVoid, kTypeString, NULL); 57 | jmethodID throwableCtor = env->GetMethodID(throwableClazz, "", signature.c_str()); 58 | if (throwableCtor == NULL) { 59 | JavaExceptionUtils::throwRuntimeException(env, "Could not find Throwable constructor"); 60 | return NULL; 61 | } 62 | 63 | // Construct error message 64 | va_list arguments; 65 | va_start(arguments, message); 66 | char formattedMessage[kExceptionMaxLength]; 67 | vsnprintf(formattedMessage, kExceptionMaxLength, message, arguments); 68 | va_end(arguments); 69 | JavaString javaMessage(formattedMessage); 70 | JniLocalRef result = env->NewObject(throwableClazz, throwableCtor, javaMessage.toJavaString(env).get()); 71 | JavaExceptionUtils::checkException(env); 72 | if (result == NULL) { 73 | JavaExceptionUtils::throwRuntimeException(env, "Could not create new Throwable instance"); 74 | return NULL; 75 | } 76 | 77 | return result; 78 | } 79 | 80 | void JavaExceptionUtils::throwExceptionOfType(JNIEnv *env, const char *exception_class_name, const char *message, va_list arguments) { 81 | jclass clazz = JavaClassUtils::findClass(env, exception_class_name, false); 82 | checkException(env); 83 | if (clazz == NULL) { 84 | #if ENABLE_EXCEPTIONS 85 | std::stringstream fatalErrorMessage; 86 | fatalErrorMessage << "Could not throw exception of type '" << exception_class_name << "'"; 87 | env->FatalError(fatalErrorMessage.str().c_str()); 88 | #endif 89 | return; 90 | } 91 | char exceptionMessage[kExceptionMaxLength]; 92 | vsnprintf(exceptionMessage, kExceptionMaxLength, message, arguments); 93 | LOG_ERROR("Throwing exception %s: %s", exception_class_name, exceptionMessage); 94 | #if ENABLE_EXCEPTIONS 95 | env->ThrowNew(clazz, exceptionMessage); 96 | #endif 97 | } 98 | 99 | void JavaExceptionUtils::throwExceptionOfType(JNIEnv *env, const char *exception_class_name, const char *message, ...) { 100 | va_list arguments; 101 | va_start(arguments, message); 102 | throwExceptionOfType(env, exception_class_name, message, arguments); 103 | va_end(arguments); 104 | } 105 | 106 | void JavaExceptionUtils::throwException(JNIEnv *env, const char *message, ...) { 107 | va_list arguments; 108 | va_start(arguments, message); 109 | throwExceptionOfType(env, kTypeException, message, arguments); 110 | va_end(arguments); 111 | } 112 | 113 | void JavaExceptionUtils::throwRuntimeException(JNIEnv *env, const char *message, ...) { 114 | va_list arguments; 115 | va_start(arguments, message); 116 | throwExceptionOfType(env, kTypeJavaClass(RuntimeException), message, arguments); 117 | va_end(arguments); 118 | } 119 | 120 | } // namespace jni 121 | } // namespace spotify 122 | -------------------------------------------------------------------------------- /src/test/cpp/JavaStringTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaStringTest.h" 23 | #include "JUnitUtils.h" 24 | 25 | void JavaStringTest::initialize(JNIEnv *env) { 26 | setClass(env); 27 | 28 | addNativeMethod("supportsRawStringLiterals", (void*)supportsRawStringLiterals, kTypeBool, NULL); 29 | 30 | addNativeMethod("createJavaString", (void*)createJavaString, kTypeVoid, NULL); 31 | addNativeMethod("createJavaStringFromStdString", (void*)createJavaStringFromStdString, kTypeVoid, NULL); 32 | addNativeMethod("nativeCreateJavaStringFromJavaString", (void*)nativeCreateJavaStringFromJavaString, kTypeVoid, kTypeString, NULL); 33 | 34 | addNativeMethod("nativeGetJavaString", (void*)nativeGetJavaString, kTypeString, NULL); 35 | addNativeMethod("nativeGetJavaStringWithNullChar", (void*)nativeGetJavaStringWithNullChar, kTypeString, NULL); 36 | addNativeMethod("nativeGetJavaStringUtf16", (void*)nativeGetJavaStringUtf16, kTypeString, NULL); 37 | addNativeMethod("nativeGetJavaStringUtf8", (void*)nativeGetJavaStringUtf8, kTypeString, NULL); 38 | 39 | addNativeMethod("nativeSetValue", (void*)nativeSetValue, kTypeVoid, kTypeString, NULL); 40 | addNativeMethod("nativeSetValueWithOperator", (void*)nativeSetValueWithOperator, kTypeVoid, NULL); 41 | addNativeMethod("nativeSetAndReturnValue", (void*)nativeSetAndReturnValue, kTypeString, kTypeString, NULL); 42 | 43 | registerNativeMethods(env); 44 | } 45 | 46 | bool JavaStringTest::supportsRawStringLiterals(JNIEnv *env) { 47 | #if HAS_RAW_STRING_LITERALS 48 | return true; 49 | #else 50 | return false; 51 | #endif 52 | } 53 | 54 | void JavaStringTest::createJavaString(JNIEnv *env, jobject javaThis) { 55 | JavaString javaString; 56 | JUNIT_ASSERT_EQUALS_STRING("", javaString.get()); 57 | } 58 | 59 | void JavaStringTest::createJavaStringFromStdString(JNIEnv *env, jobject javaThis) { 60 | std::string stdString = TEST_STRING; 61 | JavaString javaString(stdString); 62 | JUNIT_ASSERT_EQUALS_STRING(TEST_STRING, javaString.get()); 63 | } 64 | 65 | void JavaStringTest::nativeCreateJavaStringFromJavaString(JNIEnv *env, jobject javaThis, jobject javaString) { 66 | JavaString testString(env, (jstring)javaString); 67 | JUNIT_ASSERT_EQUALS_STRING(TEST_STRING, testString.get()); 68 | } 69 | 70 | jstring JavaStringTest::nativeGetJavaString(JNIEnv *env, jobject javaThis) { 71 | JavaString javaString(TEST_STRING); 72 | return javaString.toJavaString(env).leak(); 73 | } 74 | 75 | jstring JavaStringTest::nativeGetJavaStringWithNullChar(JNIEnv *env, jobject javaThis) { 76 | JavaString javaString(TEST_STRING_WITH_NULL_CHAR); 77 | return javaString.toJavaString(env).leak(); 78 | } 79 | 80 | jstring JavaStringTest::nativeGetJavaStringUtf16(JNIEnv *env, jobject javaThis) { 81 | // This test is disabled on the Java side. 82 | // We can't construct from utf16 strings yet. 83 | JavaString javaString; //(TEST_UTF16_STRING); 84 | return javaString.toJavaString(env).leak(); 85 | } 86 | 87 | jstring JavaStringTest::nativeGetJavaStringUtf8(JNIEnv *env, jobject javaThis) { 88 | #if HAS_RAW_STRING_LITERALS 89 | JavaString javaString(TEST_UTF8_STRING); 90 | return javaString.toJavaString(env).leak(); 91 | #else 92 | return NULL; 93 | #endif 94 | } 95 | 96 | void JavaStringTest::nativeSetValue(JNIEnv *env, jobject javaThis, jobject javaString) { 97 | JavaString testString; 98 | testString.set(env, (jstring)javaString); 99 | JUNIT_ASSERT_EQUALS_STRING(TEST_STRING, testString.get()); 100 | } 101 | 102 | void JavaStringTest::nativeSetValueWithOperator(JNIEnv *env, jobject javaThis) { 103 | JavaString testString; 104 | testString = TEST_STRING; 105 | JUNIT_ASSERT_EQUALS_STRING(TEST_STRING, testString.get()); 106 | } 107 | 108 | jstring JavaStringTest::nativeSetAndReturnValue(JNIEnv *env, jobject javaThis, jobject javaString) { 109 | JavaString testString; 110 | testString.set(env, (jstring)javaString); 111 | return testString.toJavaString(env).leak(); 112 | } 113 | -------------------------------------------------------------------------------- /src/main/cpp/JavaStringArray.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaStringArray_h__ 23 | #define __JavaStringArray_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JniLocalRef.h" 27 | #include "JavaString.h" 28 | 29 | namespace spotify { 30 | namespace jni { 31 | 32 | /** 33 | * @brief Class for holding Java String[] data 34 | * 35 | * This class is used for passing raw data arrays through JNI. Internally 36 | * the data is stored as a JavaString**. 37 | */ 38 | class EXPORT JavaStringArray { 39 | public: 40 | /** 41 | * @brief Create a new empty String array 42 | */ 43 | JavaStringArray(); 44 | 45 | /** 46 | * @brief Create a new String array with data. 47 | * @param data Pointer to data 48 | * @param numElements Number of elements in the input data 49 | * @param copyData True if the data should be copied to this instance 50 | * 51 | * See the documentation for set() regarding the ownership of data stored 52 | * in JavaStringArray instances. 53 | */ 54 | JavaStringArray(JavaString **data, const size_t numElements, bool copyData); 55 | 56 | /** 57 | * @brief Create a new String array with data from a Java String[] object 58 | * @param env JNIEnv 59 | * @param data Java String[] data 60 | */ 61 | JavaStringArray(JNIEnv *env, jobjectArray data); 62 | 63 | virtual ~JavaStringArray(); 64 | 65 | /** 66 | * @brief Get a pointer to the natively stored data 67 | */ 68 | JavaString **get() const { return _data; } 69 | 70 | /** 71 | * @brief Convert data to a Java String[] array 72 | */ 73 | JniLocalRef toJavaStringArray(JNIEnv *env) const; 74 | 75 | /** 76 | * @brief Return a pointer to the data stored by this instance 77 | * 78 | * When an instance of JavaStringArray is destroyed, it will attempt to free 79 | * the data stored internally. If this data is still needed elsewhere, 80 | * then you should call leak() or else it will be unavailable. 81 | */ 82 | JavaString **leak(); 83 | 84 | /** 85 | * @brief Store data in this instance 86 | * @param data Pointer to data 87 | * @param numElements Number of elements in the input data 88 | * @param copyData True if the data should be copied to this instance 89 | * 90 | * If copyData is true, then this JavaStringArray instance owns that data and 91 | * the original data passed to this method can be freed without worry. 92 | * However, if copyData is false, then this JavaStringArray effectively just 93 | * points to that instance instead. In either case, after setting data 94 | * in a JavaStringArray, it effectively owns that data. 95 | * 96 | * When this JavaStringArray is destroyed, it will free the data stored in it, 97 | * regardless of how it has been set. This means that if copyData was 98 | * false and that data is still needed elsewhere, then a segfault will 99 | * probably occur when attempting to access that data after this object 100 | * has been destroyed. Thus, the leak() method can be used to remedy the 101 | * situation by removing the pointer to the data so the JavaStringArray will 102 | * not free it upon destruction. 103 | * 104 | * It is obviously more efficient to not copy the data, however this can 105 | * cause problems if your code does not respect the ownership behaviors 106 | * described above. 107 | */ 108 | void set(JavaString **data, const size_t numElements, bool copyData); 109 | 110 | /** 111 | * @brief Store data in this instance from a Java String[] array 112 | * @param env JNIenv 113 | * @param data Java String[] array 114 | */ 115 | void set(JNIEnv *env, jobjectArray data); 116 | 117 | /** 118 | * @brief Get the number of elements stored in this array 119 | */ 120 | const size_t size() const { return _num_elements; } 121 | 122 | private: 123 | JavaString **_data; 124 | size_t _num_elements; 125 | 126 | void freeData(); 127 | }; 128 | 129 | } // namespace jni 130 | } // namespace spotify 131 | 132 | #endif // __JavaStringArray_h__ 133 | -------------------------------------------------------------------------------- /src/test/cpp/JavaClassUtilsTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaClassUtilsTest.h" 23 | #include "JUnitUtils.h" 24 | #include "JavaClassUtils.h" 25 | #include "TestObject.h" 26 | 27 | void JavaClassUtilsTest::initialize(JNIEnv *env) { 28 | setClass(env); 29 | addNativeMethod("findObjectClass", (void*)&findObjectClass, kTypeJavaClass(Class), NULL); 30 | addNativeMethod("findInvalidClass", (void*)&findInvalidClass, kTypeVoid, NULL); 31 | addNativeMethod("nativeFindClassWithLoader", (void*)&nativeFindClassWithLoader, kTypeJavaClass(Class), NULL); 32 | addNativeMethod("findInvalidClassWithLoader", (void*)&findInvalidClass, kTypeVoid, NULL); 33 | addNativeMethod("makeNameForSignatureWithNull", (void*)&makeNameForSignatureWithNull, kTypeVoid, NULL); 34 | addNativeMethod("makeNameForSignatureWithPrimitive", (void*)&makeNameForSignatureWithPrimitive, kTypeVoid, NULL); 35 | addNativeMethod("makeNameForSignatureWithArray", (void*)&makeNameForSignatureWithArray, kTypeVoid, NULL); 36 | addNativeMethod("makeNameForSignatureWithObject", (void*)&makeNameForSignatureWithObject, kTypeVoid, NULL); 37 | addNativeMethod("makeNameForSignatureWithJniSignature", (void*)&makeNameForSignatureWithJniSignature, kTypeVoid, NULL); 38 | addNativeMethod("makeNameForSignatureWithArrayOfObjects", (void*)&makeNameForSignatureWithArrayOfObjects, kTypeVoid, NULL); 39 | registerNativeMethods(env); 40 | } 41 | 42 | jclass JavaClassUtilsTest::findObjectClass(JNIEnv *env, jobject javaThis) { 43 | LOG_INFO("Starting test findObjectClass"); 44 | return JavaClassUtils::findClass(env, "java/lang/Object", false); 45 | } 46 | 47 | void JavaClassUtilsTest::findInvalidClass(JNIEnv *env, jobject javaThis) { 48 | JavaClassUtils::findClass(env, "invalid", false); 49 | } 50 | 51 | jclass JavaClassUtilsTest::nativeFindClassWithLoader(JNIEnv *env, jobject javaThis) { 52 | LOG_INFO("Starting test nativeFindClassWithLoader"); 53 | JavaClassUtilsTest dummy; 54 | return JavaClassUtils::findClass(env, dummy.getCanonicalName(), true); 55 | } 56 | 57 | void JavaClassUtilsTest::findInvalidClassWithLoader(JNIEnv *env, jobject javaThis) { 58 | JavaClassUtils::findClass(env, "invalid", true); 59 | } 60 | 61 | void JavaClassUtilsTest::makeNameForSignatureWithNull(JNIEnv *env, jobject javaThis) { 62 | std::string result; 63 | JavaClassUtils::makeNameForSignature(result, NULL); 64 | JUNIT_ASSERT_EQUALS_STRING("", result); 65 | } 66 | 67 | void JavaClassUtilsTest::makeNameForSignatureWithPrimitive(JNIEnv *env, jobject javaThis) { 68 | std::string result; 69 | JavaClassUtils::makeNameForSignature(result, kTypeInt); 70 | JUNIT_ASSERT_EQUALS_STRING(kTypeInt, result); 71 | } 72 | 73 | void JavaClassUtilsTest::makeNameForSignatureWithArray(JNIEnv *env, jobject javaThis) { 74 | std::string result; 75 | JavaClassUtils::makeNameForSignature(result, kTypeArray(kTypeInt)); 76 | JUNIT_ASSERT_EQUALS_STRING(kTypeArray(kTypeInt), result); 77 | } 78 | 79 | void JavaClassUtilsTest::makeNameForSignatureWithObject(JNIEnv *env, jobject javaThis) { 80 | std::string result; 81 | TestObject testObject; 82 | JavaClassUtils::makeNameForSignature(result, testObject.getCanonicalName()); 83 | std::stringstream expected; 84 | expected << "L" << testObject.getCanonicalName() << ";"; 85 | JUNIT_ASSERT_EQUALS_STRING(expected.str().c_str(), result); 86 | } 87 | 88 | void JavaClassUtilsTest::makeNameForSignatureWithJniSignature(JNIEnv *env, jobject javaThis) { 89 | std::string result; 90 | TestObject testObject; 91 | std::stringstream signature; 92 | signature << "L" << testObject.getCanonicalName() << ";"; 93 | JavaClassUtils::makeNameForSignature(result, signature.str().c_str()); 94 | JUNIT_ASSERT_EQUALS_STRING(signature.str().c_str(), result); 95 | } 96 | 97 | void JavaClassUtilsTest::makeNameForSignatureWithArrayOfObjects(JNIEnv *env, jobject javaThis) { 98 | std::string result; 99 | TestObject testObject; 100 | JavaClassUtils::makeNameForSignature(result, kTypeObjectArray(MAKE_CANONICAL_NAME(PACKAGE, TestObject))); 101 | std::stringstream expected; 102 | expected << "[L" << testObject.getCanonicalName() << ";"; 103 | JUNIT_ASSERT_EQUALS_STRING(expected.str().c_str(), result); 104 | } 105 | -------------------------------------------------------------------------------- /src/main/cpp/NativeObject.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "NativeObject.h" 23 | #include "JavaClassUtils.h" 24 | #include "JavaExceptionUtils.h" 25 | 26 | namespace spotify { 27 | namespace jni { 28 | 29 | NativeObject::NativeObject() {} 30 | NativeObject::NativeObject(JNIEnv *env) : JavaClass(env) {} 31 | NativeObject::~NativeObject() {} 32 | 33 | bool NativeObject::isInitialized() const { 34 | bool result = JavaClass::isInitialized(); 35 | if (result) { 36 | // We expect the persisted field to be cached, otherwise searching for persisted 37 | // fields in non-persisted classes will throw java.lang.NoSuchField exception. :( 38 | const std::string key(PERSIST_FIELD_NAME); 39 | FieldMap::const_iterator mapFindIter = _fields->find(key); 40 | return mapFindIter != _fields->end(); 41 | } 42 | return false; 43 | } 44 | 45 | jobject NativeObject::toJavaObject(JNIEnv *env) { 46 | return JavaClass::toJavaObject(env); 47 | } 48 | 49 | jobject NativeObject::toJavaObject(JNIEnv *env, jobject javaThis) { 50 | jobject result = JavaClass::toJavaObject(env, javaThis); 51 | // Persist the current object address to the Java instance 52 | if (persist(env, javaThis)) { 53 | return result; 54 | } 55 | return NULL; 56 | } 57 | 58 | bool NativeObject::persist(JNIEnv *env, jobject javaThis) { 59 | if (isInitialized()) { 60 | LOG_DEBUG("Persisting instance of '%s' to Java object", getSimpleName()); 61 | if (javaThis == NULL) { 62 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 63 | "Cannot persist object without corresponding Java instance"); 64 | return false; 65 | } 66 | jlong resultPtr = reinterpret_cast(this); 67 | env->SetLongField(javaThis, getField(PERSIST_FIELD_NAME), resultPtr); 68 | JavaExceptionUtils::checkException(env); 69 | LOG_DEBUG("Persist was successful"); 70 | return true; 71 | } 72 | return false; 73 | } 74 | 75 | JavaClass* NativeObject::getPersistedInstance(JNIEnv *env, jobject javaThis) const { 76 | if (isInitialized()) { 77 | LOG_DEBUG("Retrieving persisted instance of '%s'", getSimpleName()); 78 | jlong resultPtr = env->GetLongField(javaThis, getField(PERSIST_FIELD_NAME)); 79 | return reinterpret_cast(resultPtr); 80 | } else { 81 | return NULL; 82 | } 83 | } 84 | 85 | void NativeObject::destroy(JNIEnv *env, jobject javaThis) { 86 | if (isInitialized()) { 87 | LOG_DEBUG("Destroying persisted instance of '%s'", getSimpleName()); 88 | if (javaThis == NULL) { 89 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 90 | "Cannot destroy persisted object without corresponding Java instance"); 91 | return; 92 | } 93 | 94 | jfieldID persistField = getField(PERSIST_FIELD_NAME); 95 | if (persistField == NULL) { 96 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalStateException, 97 | "Cannot destroy, object lacks persist field"); 98 | return; 99 | } 100 | 101 | jlong resultPtr = env->GetLongField(javaThis, persistField); 102 | JavaClass *instance = reinterpret_cast(resultPtr); 103 | if (instance != NULL) { 104 | delete instance; 105 | env->SetLongField(javaThis, persistField, 0); 106 | } 107 | } 108 | } 109 | 110 | void NativeObject::setClass(JNIEnv *env) { 111 | JavaClass::setClass(env); 112 | // We can't call cacheField() here because it checks isInitialized(), which we 113 | // have overridden to check to see that the persist field is present... 114 | std::string fieldTypeSignature; 115 | JavaClassUtils::makeNameForSignature(fieldTypeSignature, kTypeLong); 116 | jfieldID field = env->GetFieldID(_clazz_global.get(), PERSIST_FIELD_NAME, fieldTypeSignature.c_str()); 117 | JavaExceptionUtils::checkException(env); 118 | if (field != NULL) { 119 | _fields_global[PERSIST_FIELD_NAME] = field; 120 | } else { 121 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaClass(NoSuchFieldError), 122 | "Persisted field '%s' (type '%s') not found on class %s", 123 | PERSIST_FIELD_NAME, kTypeLong, getCanonicalName()); 124 | } 125 | } 126 | 127 | } // namespace jni 128 | } // namespace spotify 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JniHelpers 2 | ========== 3 | 4 | JniHelpers is a library designed to facilitate writing JNI code with C++. 5 | It provides the following features: 6 | 7 | * Registry for cached class info (ie, jclass, jmethodID and jfieldID) lookups 8 | * "Automatic" conversion between Java <--> C++ objects. Note the ironic quotes 9 | around "automatic", which means: 10 | - Conversion must be enabled for the given class. This is as a feature, not 11 | a limitation, and means that classes may be represented by a Java/C++ 12 | counterpart, but you may not necessarily care to copy data from them in 13 | every JNI call. JniHelpers can do this for you, but you can also do it 14 | manally if performance is a concern. 15 | - Conversions are not performed automatically in every native callback. 16 | However, getting a C++ instance of a Java class or creating a Java 17 | instance of a C++ class is trivial. 18 | - JniHelpers prefers to copy memory, thus avoiding tricky issues that arise 19 | when trying to mix the very different memory management models of the Java 20 | garbage collector and C++ RAII paradigm. 21 | * Conversion between Java `String` objects and `stl::string`. 22 | * Conversion between Java `byte` arrays and C `void*` arrays. 23 | * Persisting native objects through Java without copying data. Persisted 24 | objects are created on the heap and leaked; their memory address is stored 25 | in a corresponding Java instance in a `long` field. If you want to access 26 | the native instance again, a call is provided to fetch the native instance 27 | from the field. This is very useful for creating long-lived native objects 28 | without having to make them singletons. 29 | * Easy registration of native methods. Rather than having to memorize the 30 | complex rules of creating JNI type signatures, a simple varargs interface is 31 | provided to do the grunt work for you. 32 | * Conveniences for common JNI functions, including: 33 | - Lookup Java classes with a given classloader 34 | - Attach/detach native threads to Java 35 | - Throw Java exceptions from native code, with printf-style string 36 | formatting 37 | - RAII template classes for JNI local/global/weak global references. 38 | * Android-friendly. JniHelpers was designed to work well on Android, and 39 | although it also runs fine on desktop Java, the CMake configuration is meant 40 | to be easily adapted for an Android build. 41 | * Tested! There is a test suite provided which is executed from Java that will 42 | run the native tests and prove communication across JNI works as expected. 43 | * Documented! Extensive class documentation is provided. 44 | 45 | 46 | Examples 47 | -------- 48 | 49 | The best examples for JniHelpers is the test suite itself. See the classes 50 | within `src/test/cpp`, they are documented and explain correct usage of the 51 | library. 52 | 53 | 54 | What JniHelpers is NOT 55 | ---------------------- 56 | 57 | JniHelpers is *not* SWIG. It does not attempt to automatically create wrappers 58 | around native code. 59 | 60 | JniHelpers is also not a complete wrapper around JNI. It does not attempt to 61 | completely shield the programmer from all JNI calls. Rather, it just makes the 62 | trickiest parts of JNI a bit friendlier to work with. 63 | 64 | 65 | Building 66 | -------- 67 | 68 | JniHelpers contains both Java and C++ code, and is built with Gradle and CMake, 69 | respectively. To build JniHelpers as a static library, just run the following 70 | commands: 71 | 72 | $ mkdir build 73 | $ cd build 74 | $ cmake .. 75 | $ make 76 | 77 | The Java part of JniHelpers can now be built as a JAR file: 78 | 79 | $ ./gradlew assemble 80 | 81 | To use JniHelpers in your project, it is not necessary to build standalone 82 | artifacts. Rather, you can use this project as a submodule in your code and 83 | include the CMake/Gradle script from your project. This is the preferred 84 | method for using the library. 85 | 86 | Building JniHelpers on Windows has not been tested and may not work. The project 87 | is known to build for desktop Linux, Mac OS X, and Android. 88 | 89 | 90 | Testing 91 | ------- 92 | 93 | JniHelpers has test cases, which are implemented in JUnit. If you wish to submit 94 | a pull request to this project, please add tests for any new code or bugfixes, 95 | and make sure that the existing tests continue to pass before sending the 96 | request. Pull requests with failing tests will not be merged! 97 | 98 | Unfortunately, there is not yet integration between the two Gradle and CMake 99 | build system, so one must run them independently to execute the tests (we aim to 100 | fix this in a future release so that Gradle can execute the CMake build on its 101 | own). First, one must build the C++ parts with CMake as detailed above in the 102 | "Building" section. After the JniHelpers libraries have been built, one can run 103 | the JUnit tests from the top-level project directory like so: 104 | 105 | $ ./gradlew test 106 | 107 | If all goes well, Gradle should report "BUILD SUCCESSFUL" and no other output. 108 | Any test failures will be reported to the command line. 109 | 110 | 111 | License 112 | ------- 113 | 114 | JniHelpers is made available under the Apache 2.0 license. For more information, 115 | please see the LICENSE.txt file distributed with JniHelpers. 116 | -------------------------------------------------------------------------------- /src/main/cpp/JavaClassUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __JavaClassUtils_h__ 23 | #define __JavaClassUtils_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include 27 | #include 28 | 29 | namespace spotify { 30 | namespace jni { 31 | 32 | /** 33 | * @brief Convenience macro to create canonical name for a class 34 | * 35 | * Note that the class name should *not* be a quoted string, otherwise the quotes 36 | * will be added literally to the string. Example usage: 37 | * 38 | * #define PACKAGE "com/example/stuff" 39 | * MAKE_CANONICAL_NAME(PACKAGE, Foo) 40 | * 41 | * In the above example, the PACKAGE definition should ideally be placed in a header 42 | * file which is accessible to your package sources. The above macro will then produce 43 | * the string "com/example/stuff/Foo". 44 | */ 45 | #define MAKE_CANONICAL_NAME(_PACKAGE, _CLASS) _PACKAGE "/" #_CLASS 46 | 47 | class JavaClassUtils { 48 | private: 49 | // Direct instantiation is not allowed for this class 50 | JavaClassUtils() {} 51 | JavaClassUtils(const JavaClassUtils&) {} 52 | virtual ~JavaClassUtils() {} 53 | 54 | public: 55 | /** 56 | * @brief Cache an instance of the Java ClassLoader 57 | * @param env JNIEnv 58 | * 59 | * If you intend to use findClass() with a Java ClassLoader, then this method will be 60 | * invoked automatically to initialize a static JavaClass instance of this class, 61 | * which is needed by findClass() to do the actual lookup. Since initialization of 62 | * JavaClass instances may take a bit of time, this method is exposed in case you 63 | * wish to manually call it during JNI initialization. 64 | * 65 | * In most cases, it should not be necessary to explicitly call this method from your 66 | * code. 67 | */ 68 | static EXPORT void setJavaClassLoader(JNIEnv *env); 69 | 70 | /** 71 | * @brief Look up a Java class object 72 | * @param env JNIEnv 73 | * @param class_name Canonical class name 74 | * @param use_class_loader Use a Java ClassLoader instance. This is not necessary for 75 | * looking up Java classes, however it may be needed in other 76 | * cases, particularly when trying to load your own classes in 77 | * Android. 78 | * @return Java class object, or NULL if none was found 79 | * 80 | * This method will throw NoClassDefFoundError if the class could not be found. 81 | */ 82 | static EXPORT jclass findClass(JNIEnv *env, const char *class_name, bool use_class_loader); 83 | 84 | /** 85 | * @brief Make a name safe to pass to a method requiring JNI signatures 86 | * @param receiver String to recieve the generated signature 87 | * @param name Name to convert 88 | * 89 | * When looking up field or method names which involve another class name, it may be 90 | * necessary to convert these to the corresponding JNI signature types (for example, 91 | * Lcom/example/MyClass; for com.example.MyClass). This method will create a JNI 92 | * signature string based on a canonical name. 93 | * 94 | * Passing a JNI signature to this class should have no effect, it will simply return 95 | * the same string to the receiver. 96 | */ 97 | static EXPORT void makeNameForSignature(std::string &receiver, const char *name); 98 | 99 | /** 100 | * @brief Make a method signature from a variable list of arguments 101 | * @param receiver String to receive the generated signature 102 | * @param return_type Return type of function (see JniTypes.h) 103 | * @param ... List of arguments which function takes, ending with NULL. If the method 104 | * takes no arguments, just pass NULL here. If you do not pass NULL as the 105 | * last argument to this method, unexpected behavior will occur! 106 | */ 107 | static EXPORT void makeSignature(std::string &receiver, const char *return_type, ...); 108 | 109 | /** 110 | * @brief Make a method signature from a variable list of arguments 111 | * @param receiver String to receive the generated signature 112 | * @param return_type Return type of function (see JniTypes.h) 113 | * @param arguments List of arguments which function takes 114 | */ 115 | static EXPORT void makeSignatureWithList(std::string &receiver, const char *return_type, va_list arguments); 116 | }; 117 | 118 | } // namespace jni 119 | } // namespace spotify 120 | 121 | #endif // __JavaClassUtils_h__ 122 | -------------------------------------------------------------------------------- /src/test/cpp/JUnitUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | /** 23 | * This file contains various macros to help testing native code with 24 | * JUnit test runner. Even though the pure C++ code could be tested with 25 | * a C++ unit test runner, it's easier to just run all the tests from 26 | * Java in an IDE rather than having two test suites. 27 | */ 28 | 29 | #ifndef __JUnitUtils_h__ 30 | #define __JUnitUtils_h__ 31 | 32 | #include "JniHelpers.h" 33 | #include "TestConstants.h" 34 | #include 35 | #include 36 | 37 | using namespace spotify::jni; 38 | 39 | #define kTypeJavaAssertion kTypeJavaClass(AssertionError) 40 | #define DEFAULT_FLOAT_TOLERANCE 0.01f 41 | 42 | #define _JUNIT_ASSERT_TRUE(_RESULT, _FILE, _LINE) { \ 43 | if (!_RESULT) { \ 44 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 45 | "expected true but was false (at %s:%d)", _FILE, _LINE); \ 46 | } \ 47 | } 48 | #define JUNIT_ASSERT_TRUE(_R) _JUNIT_ASSERT_TRUE(_R, __FILE__, __LINE__) 49 | 50 | #define _JUNIT_ASSERT_FALSE(_RESULT, _FILE, _LINE) { \ 51 | if (_RESULT) { \ 52 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 53 | "expected false but was true (at %s:%d)", _FILE, _LINE); \ 54 | } \ 55 | } 56 | #define JUNIT_ASSERT_FALSE(_R) _JUNIT_ASSERT_FALSE(_R, __FILE__, __LINE__) 57 | 58 | #define JUNIT_ASSERT_EQUALS_BOOL(_E, _R) _JUNIT_ASSERT_TRUE(_E &&_R, __FILE__, __LINE__) 59 | 60 | #define _JUNIT_ASSERT_NOT_NULL(_RESULT, _FILE, _LINE) { \ 61 | if (_RESULT == NULL) { \ 62 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 63 | "expected but was (at %s:%d)", _FILE, _LINE); \ 64 | } \ 65 | } 66 | #define JUNIT_ASSERT_NOT_NULL(_R) _JUNIT_ASSERT_NOT_NULL(_R, __FILE__, __LINE__) 67 | 68 | #define _JUNIT_ASSERT_NULL(_RESULT, _FILE, _LINE) { \ 69 | if (_RESULT != NULL) { \ 70 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 71 | "expected but was (at %s:%d)", _FILE, _LINE); \ 72 | } \ 73 | } 74 | #define JUNIT_ASSERT_NULL(_R) _JUNIT_ASSERT_NULL(_R, __FILE__, __LINE__) 75 | 76 | #define _JUNIT_ASSERT_EQUALS_INT(_EXPECTED, _RESULT, _FILE, _LINE) { \ 77 | if (_EXPECTED != _RESULT) { \ 78 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 79 | "expected %d but was: %d (at %s:%d)", _EXPECTED, _RESULT, _FILE, _LINE); \ 80 | } \ 81 | } 82 | #define JUNIT_ASSERT_EQUALS_INT(_E, _R) _JUNIT_ASSERT_EQUALS_INT(_E, _R, __FILE__, __LINE__) 83 | 84 | #define _JUNIT_ASSERT_EQUALS_FLOAT(_EXPECTED, _RESULT, _TOLERANCE, _FILE, _LINE) { \ 85 | if (fabs(_EXPECTED - _RESULT) > _TOLERANCE) { \ 86 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 87 | "expected %f but was: %f (at %s:%d)", _EXPECTED, _RESULT, _FILE, _LINE); \ 88 | } \ 89 | } 90 | #define JUNIT_ASSERT_EQUALS_FLOAT(_E, _R, _T) _JUNIT_ASSERT_EQUALS_FLOAT(_E, _R, _T, __FILE__, __LINE__) 91 | 92 | #define _JUNIT_ASSERT_EQUALS_CSTRING(_EXPECTED, _RESULT, _FILE, _LINE) { \ 93 | if (strcmp(_EXPECTED, _RESULT) != 0) { \ 94 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 95 | "expected <%s> but was: <%s> (at %s:%d)", _EXPECTED, _RESULT, _FILE, _LINE); \ 96 | } \ 97 | } 98 | #define JUNIT_ASSERT_EQUALS_CSTRING(_E, _R) _JUNIT_ASSERT_EQUALS_CSTRING(_E, _R, __FILE__, __LINE__) 99 | 100 | #define _JUNIT_ASSERT_EQUALS_STRING(_EXPECTED, _RESULT, _FILE, _LINE) { \ 101 | if (strcmp(_EXPECTED, _RESULT.c_str()) != 0) { \ 102 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 103 | "expected <%s> but was: <%s> (at %s:%d)", _EXPECTED, _RESULT.c_str(), _FILE, _LINE); \ 104 | } \ 105 | } 106 | #define JUNIT_ASSERT_EQUALS_STRING(_E, _R) _JUNIT_ASSERT_EQUALS_STRING(_E, _R, __FILE__, __LINE__) 107 | 108 | #define _JUNIT_ASSERT_EQUALS_ARRAY(_EXPECTED, _RESULT, _SIZE, _FILE, _LINE) { \ 109 | if (memcmp(_EXPECTED, _RESULT, _SIZE) != 0) { \ 110 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 111 | "(at %s:%d)", _FILE, _LINE); \ 112 | } \ 113 | } 114 | #define JUNIT_ASSERT_EQUALS_ARRAY(_E, _R, _S) _JUNIT_ASSERT_EQUALS_ARRAY(_E, _R, _S, __FILE__, __LINE__) 115 | 116 | #define _JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(_EXPECTED, _RESULT, _SIZE, _FILE, _LINE) { \ 117 | for (size_t i = 0; i < _SIZE; i++) { \ 118 | if (_EXPECTED[i]->get() != _RESULT[i]->get()) { \ 119 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaAssertion, \ 120 | "expected <%s> but was: <%s> (at %s:%d)", _EXPECTED[i]->get().c_str(), _RESULT[i]->get().c_str(), _FILE, _LINE); \ 121 | } \ 122 | } \ 123 | } 124 | #define JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(_E, _R, _S) _JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(_E, _R, _S, __FILE__, __LINE__) 125 | 126 | #endif // __JUnitUtils_h__ 127 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /src/test/cpp/NativeObjectTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "NativeObjectTest.h" 23 | #include "JUnitUtils.h" 24 | #include "PersistedObject.h" 25 | #include "TestObject.h" 26 | 27 | void NativeObjectTest::initialize(JNIEnv *env) { 28 | setClass(env); 29 | 30 | PersistedObject persistedObject; 31 | const char* persistedObjectName = persistedObject.getCanonicalName(); 32 | 33 | addNativeMethod("createPersistedObject", (void*)&createPersistedObject, persistedObjectName, NULL); 34 | addNativeMethod("getPersistedInstance", (void*)&getPersistedInstance, persistedObjectName, persistedObjectName, NULL); 35 | addNativeMethod("nativeIsPersistenceEnabled", (void*)nativeIsPersistenceEnabled, kTypeVoid, NULL); 36 | addNativeMethod("isPersistenceEnabledWithoutInit", (void*)isPersistenceEnabledWithoutInit, kTypeVoid, NULL); 37 | addNativeMethod("destroyPersistedObject", (void*)&destroyPersistedObject, kTypeVoid, persistedObjectName, NULL); 38 | addNativeMethod("persistInvalidClass", (void*)&persistInvalidClass, kTypeVoid, NULL); 39 | addNativeMethod("persistNullObject", (void*)&persistNullObject, kTypeVoid, NULL); 40 | addNativeMethod("destroyInvalidClass", (void*)&destroyInvalidClass, kTypeVoid, NULL); 41 | addNativeMethod("destroyNullObject", (void*)&destroyNullObject, kTypeVoid, NULL); 42 | 43 | registerNativeMethods(env); 44 | } 45 | 46 | jobject NativeObjectTest::createPersistedObject(JNIEnv *env, jobject javaThis) { 47 | LOG_INFO("Starting test: createPersistedObject"); 48 | PersistedObject *persistedObject = new PersistedObject(env); 49 | persistedObject->i = TEST_INTEGER; 50 | // Persist should be called for us here. Note that the original object is leaked; it will 51 | // be cleaned up in destroyPersistedObject(). 52 | return persistedObject->toJavaObject(env); 53 | } 54 | 55 | jobject NativeObjectTest::getPersistedInstance(JNIEnv *env, jobject javaThis, jobject object) { 56 | LOG_INFO("Starting test: getPersistedInstance"); 57 | ClassRegistry registry; 58 | registry.add(env, new PersistedObject(env)); 59 | PersistedObject *persistedObject = registry.getNativeInstance(env, object); 60 | JUNIT_ASSERT_EQUALS_INT(TEST_INTEGER, persistedObject->i); 61 | JUNIT_ASSERT_NOT_NULL(persistedObject->getCanonicalName()); 62 | JUNIT_ASSERT_TRUE(persistedObject->isInitialized()); 63 | return persistedObject->toJavaObject(env); 64 | } 65 | 66 | void NativeObjectTest::nativeIsPersistenceEnabled(JNIEnv *env, jobject javaThis) { 67 | LOG_INFO("Starting test: nativeIsPersistenceEnabled"); 68 | PersistedObject persistedObject(env); 69 | JUNIT_ASSERT_TRUE(persistedObject.isInitialized()); 70 | PersistedObject mergedObject; 71 | mergedObject.merge(&persistedObject); 72 | JUNIT_ASSERT_TRUE(mergedObject.isInitialized()); 73 | } 74 | 75 | void NativeObjectTest::isPersistenceEnabledWithoutInit(JNIEnv *env, jobject javaThis) { 76 | PersistedObject persistedObject; 77 | JUNIT_ASSERT_FALSE(persistedObject.isInitialized()); 78 | } 79 | 80 | void NativeObjectTest::destroyPersistedObject(JNIEnv *env, jobject javaThis, jobject object) { 81 | LOG_INFO("Starting test: destroyPersistedObject"); 82 | ClassRegistry registry; 83 | registry.add(env, new PersistedObject(env)); 84 | PersistedObject *persistedObject = registry.getNativeInstance(env, object); 85 | persistedObject->destroy(env, object); 86 | } 87 | 88 | void NativeObjectTest::persistInvalidClass(JNIEnv *env, jobject javaThis) { 89 | LOG_INFO("Starting test: persistInvalidClass"); 90 | PersistedObject persistedObject(env); 91 | persistedObject.mapFields(); 92 | persistedObject.persist(env, javaThis); 93 | } 94 | 95 | void NativeObjectTest::persistNullObject(JNIEnv *env, jobject javaThis) { 96 | LOG_INFO("Starting test: persistNullObject"); 97 | PersistedObject persistedObject(env); 98 | persistedObject.mapFields(); 99 | JUNIT_ASSERT_FALSE(persistedObject.persist(env, NULL)); 100 | } 101 | 102 | void NativeObjectTest::destroyInvalidClass(JNIEnv *env, jobject javaThis) { 103 | // This test is almost impossible to replicate from Java, and frankly should 104 | // not happen from (responsible) C++ code either. It would be possible to catch 105 | // if we are willing to do fieldID lookups on the fly rather than cached, but 106 | // that assumes that performance is not an issue here. For that reason, this 107 | // test is excluded and the erroneous behavior will (and probably should) crash 108 | // the JVM if enabled. 109 | #if 0 110 | LOG_INFO("Starting test: destroyInvalidClass"); 111 | PersistedObject persistedObject(env); 112 | persistedObject.mapFields(); 113 | persistedObject.destroy(env, javaThis); 114 | #endif 115 | } 116 | 117 | void NativeObjectTest::destroyNullObject(JNIEnv *env, jobject javaThis) { 118 | LOG_INFO("Starting test: destroyNullObject"); 119 | PersistedObject persistedObject(env); 120 | persistedObject.mapFields(); 121 | persistedObject.destroy(env, NULL); 122 | } 123 | -------------------------------------------------------------------------------- /src/test/cpp/ByteArrayTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "ByteArrayTest.h" 23 | #include "JUnitUtils.h" 24 | #include "ByteArray.h" 25 | 26 | void ByteArrayTest::initialize(JNIEnv *env) { 27 | setClass(env); 28 | 29 | addNativeMethod("createNewByteArray", (void*)createNewByteArray, kTypeVoid, NULL); 30 | addNativeMethod("createNewByteArrayWithData", (void*)createNewByteArrayWithData, kTypeVoid, NULL); 31 | addNativeMethod("createNewByteArrayWithDataCopy", (void*)createNewByteArrayWithData, kTypeVoid, NULL); 32 | addNativeMethod("nativeCreateNewByteArrayWithJavaData", (void*)nativeCreateNewByteArrayWithJavaData, kTypeVoid, kTypeArray(kTypeByte), NULL); 33 | addNativeMethod("createNewByteArrayWithNull", (void*)createNewByteArrayWithNull, kTypeVoid, NULL); 34 | addNativeMethod("createNewByteArrayWithNullAndNonZeroLength", (void*)createNewByteArrayWithNullAndNonZeroLength, kTypeVoid, NULL); 35 | addNativeMethod("nativeGetTestJavaByteArray", (void*)nativeGetTestJavaByteArray, kTypeArray(kTypeByte), NULL); 36 | addNativeMethod("setData", (void*)setData, kTypeVoid, NULL); 37 | addNativeMethod("setDataWithCopy", (void*)setData, kTypeVoid, NULL); 38 | addNativeMethod("nativeSetJavaByteArray", (void*)nativeSetJavaByteArray, kTypeVoid, kTypeArray(kTypeByte), kTypeInt, NULL); 39 | 40 | registerNativeMethods(env); 41 | } 42 | 43 | void* ByteArrayTest::getTestData() { 44 | char *result = (char*)malloc(getTestDataSize()); 45 | for (size_t i = 0; i < getTestDataSize(); i++) { 46 | result[i] = (char)i; 47 | } 48 | return result; 49 | } 50 | 51 | size_t ByteArrayTest::getTestDataSize() { 52 | return 10; 53 | } 54 | 55 | void ByteArrayTest::createNewByteArray(JNIEnv *env, jobject javaThis) { 56 | ByteArray byteArray; 57 | JUNIT_ASSERT_EQUALS_INT(0, byteArray.size()); 58 | JUNIT_ASSERT_NULL(byteArray.get()); 59 | } 60 | 61 | void ByteArrayTest::createNewByteArrayWithData(JNIEnv *env, jobject javaThis) { 62 | void *data = getTestData(); 63 | ByteArray byteArray(data, getTestDataSize(), false); 64 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), byteArray.size()); 65 | JUNIT_ASSERT_EQUALS_ARRAY(data, byteArray.get(), byteArray.size()); 66 | } 67 | 68 | void ByteArrayTest::createNewByteArrayWithDataCopy(JNIEnv *env, jobject javaThis) { 69 | void *data = getTestData(); 70 | ByteArray byteArray(data, getTestDataSize(), true); 71 | free(data); 72 | void *expected = getTestData(); 73 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), byteArray.size()); 74 | JUNIT_ASSERT_EQUALS_ARRAY(expected, byteArray.get(), byteArray.size()); 75 | } 76 | 77 | void ByteArrayTest::nativeCreateNewByteArrayWithJavaData(JNIEnv *env, jobject javaThis, jbyteArray javaData) { 78 | void *data = getTestData(); 79 | ByteArray byteArray(env, javaData); 80 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), byteArray.size()); 81 | JUNIT_ASSERT_EQUALS_ARRAY(data, byteArray.get(), byteArray.size()); 82 | } 83 | 84 | void ByteArrayTest::createNewByteArrayWithNull(JNIEnv *env, jobject javaThis) { 85 | ByteArray byteArray(env, (jbyteArray)NULL); 86 | JUNIT_ASSERT_EQUALS_INT(0, byteArray.size()); 87 | JUNIT_ASSERT_NULL(byteArray.get()); 88 | } 89 | 90 | void ByteArrayTest::createNewByteArrayWithNullAndNonZeroLength(JNIEnv *env, jobject javaThis) { 91 | ByteArray byteArray(NULL, 1, false); 92 | JUNIT_ASSERT_EQUALS_INT(0, byteArray.size()); 93 | JUNIT_ASSERT_NULL(byteArray.get()); 94 | } 95 | 96 | jbyteArray ByteArrayTest::nativeGetTestJavaByteArray(JNIEnv *env, jobject javaThis) { 97 | void *data = getTestData(); 98 | ByteArray byteArray(data, getTestDataSize(), true); 99 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), byteArray.size()); 100 | JUNIT_ASSERT_EQUALS_ARRAY(data, byteArray.get(), byteArray.size()); 101 | JniLocalRef result = byteArray.toJavaByteArray(env); 102 | return result.leak(); 103 | } 104 | 105 | void ByteArrayTest::setData(JNIEnv *env, jobject javaThis) { 106 | void *data = getTestData(); 107 | ByteArray byteArray; 108 | byteArray.set(data, getTestDataSize(), false); 109 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), byteArray.size()); 110 | JUNIT_ASSERT_EQUALS_ARRAY(data, byteArray.get(), byteArray.size()); 111 | } 112 | 113 | void ByteArrayTest::setDataWithCopy(JNIEnv *env, jobject javaThis) { 114 | void *data = getTestData(); 115 | ByteArray byteArray; 116 | byteArray.set(data, getTestDataSize(), true); 117 | // Write 0's over the original data to make sure that a false positive 118 | // doesn't cause the test to pass. 119 | memset(data, 0, getTestDataSize()); 120 | void *expectedData = getTestData(); 121 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), byteArray.size()); 122 | JUNIT_ASSERT_EQUALS_ARRAY(expectedData, byteArray.get(), byteArray.size()); 123 | free(expectedData); 124 | } 125 | 126 | void ByteArrayTest::nativeSetJavaByteArray(JNIEnv *env, jobject javaThis, jbyteArray javaData, jint expectedSize) { 127 | void *data = getTestData(); 128 | ByteArray byteArray; 129 | byteArray.set(env, javaData); 130 | JUNIT_ASSERT_EQUALS_INT(expectedSize, byteArray.size()); 131 | JUNIT_ASSERT_EQUALS_ARRAY(data, byteArray.get(), byteArray.size()); 132 | } 133 | -------------------------------------------------------------------------------- /src/test/cpp/JavaStringArrayTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaStringArrayTest.h" 23 | #include "JUnitUtils.h" 24 | #include "JavaStringArray.h" 25 | 26 | void JavaStringArrayTest::initialize(JNIEnv *env) { 27 | setClass(env); 28 | 29 | addNativeMethod("createNewJavaStringArray", (void*)createNewJavaStringArray, kTypeVoid, NULL); 30 | addNativeMethod("createNewJavaStringArrayWithData", (void*)createNewJavaStringArrayWithData, kTypeVoid, NULL); 31 | addNativeMethod("createNewJavaStringArrayWithDataCopy", (void*)createNewJavaStringArrayWithData, kTypeVoid, NULL); 32 | addNativeMethod("nativeCreateNewJavaStringArrayWithJavaData", (void*)nativeCreateNewJavaStringArrayWithJavaData, kTypeVoid, kTypeObjectArray(kTypeString), NULL); 33 | addNativeMethod("createNewJavaStringArrayWithNull", (void*)createNewJavaStringArrayWithNull, kTypeVoid, NULL); 34 | addNativeMethod("createNewJavaStringArrayWithNullAndNonZeroLength", (void*)createNewJavaStringArrayWithNullAndNonZeroLength, kTypeVoid, NULL); 35 | addNativeMethod("nativeGetTestJavaStringArray", (void*)nativeGetTestJavaStringArray, kTypeObjectArray(kTypeString), NULL); 36 | addNativeMethod("setData", (void*)setData, kTypeVoid, NULL); 37 | addNativeMethod("setDataWithCopy", (void*)setData, kTypeVoid, NULL); 38 | addNativeMethod("nativeSetJavaStringArray", (void*)nativeSetJavaStringArray, kTypeVoid, kTypeObjectArray(kTypeString), kTypeInt, NULL); 39 | 40 | registerNativeMethods(env); 41 | } 42 | 43 | JavaString **JavaStringArrayTest::getTestData() { 44 | JavaString **result = (JavaString **)malloc(getTestDataSize() * sizeof(JavaString*)); 45 | result[0] = new JavaString("abc"); 46 | result[1] = new JavaString("def"); 47 | result[2] = new JavaString("ghi"); 48 | result[3] = new JavaString("ążń"); 49 | return result; 50 | } 51 | 52 | size_t JavaStringArrayTest::getTestDataSize() { 53 | return 4; 54 | } 55 | 56 | void JavaStringArrayTest::createNewJavaStringArray(JNIEnv *env, jobject javaThis) { 57 | JavaStringArray JavaStringArray; 58 | JUNIT_ASSERT_EQUALS_INT(0, JavaStringArray.size()); 59 | JUNIT_ASSERT_NULL(JavaStringArray.get()); 60 | } 61 | 62 | void JavaStringArrayTest::createNewJavaStringArrayWithData(JNIEnv *env, jobject javaThis) { 63 | JavaString **data = getTestData(); 64 | JavaStringArray JavaStringArray(data, getTestDataSize(), false); 65 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), JavaStringArray.size()); 66 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(data, JavaStringArray.get(), JavaStringArray.size()); 67 | } 68 | 69 | void JavaStringArrayTest::createNewJavaStringArrayWithDataCopy(JNIEnv *env, jobject javaThis) { 70 | JavaString **data = getTestData(); 71 | JavaStringArray JavaStringArray(data, getTestDataSize(), true); 72 | free(data); 73 | JavaString **expected = getTestData(); 74 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), JavaStringArray.size()); 75 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(expected, JavaStringArray.get(), JavaStringArray.size()); 76 | } 77 | 78 | void JavaStringArrayTest::nativeCreateNewJavaStringArrayWithJavaData(JNIEnv *env, jobject javaThis, jobjectArray javaData) { 79 | JavaString **data = getTestData(); 80 | JavaStringArray JavaStringArray(env, javaData); 81 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), JavaStringArray.size()); 82 | JUNIT_ASSERT_NOT_NULL(JavaStringArray.get()); 83 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(data, JavaStringArray.get(), JavaStringArray.size()); 84 | } 85 | 86 | void JavaStringArrayTest::createNewJavaStringArrayWithNull(JNIEnv *env, jobject javaThis) { 87 | JavaStringArray JavaStringArray(env, (jobjectArray)NULL); 88 | JUNIT_ASSERT_EQUALS_INT(0, JavaStringArray.size()); 89 | JUNIT_ASSERT_NULL(JavaStringArray.get()); 90 | } 91 | 92 | void JavaStringArrayTest::createNewJavaStringArrayWithNullAndNonZeroLength(JNIEnv *env, jobject javaThis) { 93 | JavaStringArray JavaStringArray(NULL, 1, false); 94 | JUNIT_ASSERT_EQUALS_INT(0, JavaStringArray.size()); 95 | JUNIT_ASSERT_NULL(JavaStringArray.get()); 96 | } 97 | 98 | jobjectArray JavaStringArrayTest::nativeGetTestJavaStringArray(JNIEnv *env, jobject javaThis) { 99 | JavaString **data = getTestData(); 100 | JavaStringArray JavaStringArray(data, getTestDataSize(), true); 101 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), JavaStringArray.size()); 102 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(data, JavaStringArray.get(), JavaStringArray.size()); 103 | JniLocalRef result = JavaStringArray.toJavaStringArray(env); 104 | return result.leak(); 105 | } 106 | 107 | void JavaStringArrayTest::setData(JNIEnv *env, jobject javaThis) { 108 | JavaString **data = getTestData(); 109 | JavaStringArray JavaStringArray; 110 | JavaStringArray.set(data, getTestDataSize(), false); 111 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), JavaStringArray.size()); 112 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(data, JavaStringArray.get(), JavaStringArray.size()); 113 | } 114 | 115 | void JavaStringArrayTest::setDataWithCopy(JNIEnv *env, jobject javaThis) { 116 | JavaString **data = getTestData(); 117 | JavaStringArray JavaStringArray; 118 | JavaStringArray.set(data, getTestDataSize(), true); 119 | // Write 0's over the original data to make sure that a false positive 120 | // doesn't cause the test to pass. 121 | memset(data, 0, getTestDataSize()); 122 | JavaString **expected = getTestData(); 123 | JUNIT_ASSERT_EQUALS_INT(getTestDataSize(), JavaStringArray.size()); 124 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(expected, JavaStringArray.get(), JavaStringArray.size()); 125 | free(expected); 126 | } 127 | 128 | void JavaStringArrayTest::nativeSetJavaStringArray(JNIEnv *env, jobject javaThis, jobjectArray javaData, jint expectedSize) { 129 | JavaString **data = getTestData(); 130 | JavaStringArray JavaStringArray; 131 | JavaStringArray.set(env, javaData); 132 | JUNIT_ASSERT_EQUALS_INT(expectedSize, JavaStringArray.size()); 133 | JUNIT_ASSERT_EQUALS_JAVA_STRING_ARRAY(data, JavaStringArray.get(), JavaStringArray.size()); 134 | } 135 | -------------------------------------------------------------------------------- /src/main/cpp/ClassRegistry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #ifndef __ClassRegistry_h__ 23 | #define __ClassRegistry_h__ 24 | 25 | #include "JniHelpersCommon.h" 26 | #include "JavaClass.h" 27 | #include "JavaExceptionUtils.h" 28 | #include "NativeObject.h" 29 | #include "ScopedPtr.h" 30 | #include 31 | #include 32 | #include 33 | 34 | namespace spotify { 35 | namespace jni { 36 | 37 | #if WIN32 38 | // TODO: This is a MSVC thing, should refactor to use PIMPL instead (ugh) 39 | template class EXPORT std::map >; 40 | #endif 41 | 42 | typedef std::map > ClassRegistryMap; 43 | 44 | /** 45 | * @brief Keeps a map of cached JavaClass instances 46 | * 47 | * The ClassRegistry class holds a map of bare (but initialized) JavaClass objects. 48 | * Since looking up class, field, and method definitions is somewhat expensive, it 49 | * makes sense to cache this data (and in fact, the JNI documentation also advises 50 | * doing this). 51 | * 52 | * Your program should keep an instance of the ClassRegistry either globally (with a 53 | * static object), or in some other long-lived object. During the JNI initialization 54 | * called via System.loadLibrary() in Java, you should add each JavaClass object 55 | * that you want to use in the map. Assuming that you have correctly subclassed the 56 | * JavaClass interface in your objects, they should be initialized and cache their 57 | * field and method data for quick access later. 58 | * 59 | * After the map has been configured, you can call newInstance<>() to create a new 60 | * JavaClass object of the desired type. The method/field IDs from the global map 61 | * will be merged into this object, thus making it relatively cheap to copy to/from 62 | * Java objects. 63 | */ 64 | class EXPORT ClassRegistry { 65 | public: 66 | ClassRegistry(); 67 | virtual ~ClassRegistry(); 68 | 69 | /** 70 | * @brief Add a new class to the map 71 | * @param env JNIEnv 72 | * @param item JavaClass instance to add. This instance should be properly 73 | * initialized (see the JavaClass documentation for more details). 74 | */ 75 | virtual void add(JNIEnv *env, const JavaClass *item); 76 | 77 | /** 78 | * @brief Get an instance of a JavaClass definition in the map. 79 | * @param name Canonincal class name. If NULL, this method will throw an 80 | * IllegalArgumentException to Java. 81 | * @return Pointer to instance, or NULL if no such instance exists. 82 | */ 83 | virtual const JavaClass* get(const char *name) const; 84 | virtual const JavaClass* operator[](const char *name) const { return get(name); } 85 | 86 | /** 87 | * @brief Create a new instance of a given JavaClass subclass 88 | * 89 | * This method will create a new instance of the given type, which must subclass 90 | * the JavaClass abstract class. The instance will be populated with the cached 91 | * field and method IDs that were (presumably) set during initialization. 92 | * 93 | * If local field mappings have been configured with JavaClass::mapField, then 94 | * the new instance will be populated with data from the Java object. 95 | * 96 | * @param env JNIEnv 97 | * @param fromObject Java object to copy data from. If NULL, then a new object 98 | * will be created with the default constructor and class info 99 | * from merge(). 100 | * @return New instance, or NULL if none could not be created. 101 | */ 102 | template 103 | TypeName* newInstance(JNIEnv *env, jobject fromObject) { 104 | LOG_DEBUG("Creating new instance of class with registry info"); 105 | TypeName *result = new TypeName(); 106 | const char *name = result->getCanonicalName(); 107 | if (name == NULL || strlen(name) == 0) { 108 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 109 | "Could not find canonical name for class"); 110 | delete result; 111 | return NULL; 112 | } 113 | 114 | const TypeName *classInfo = dynamic_cast(get(name)); 115 | if (classInfo == NULL) { 116 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalStateException, 117 | "No class information registered for '%s'", name); 118 | delete result; 119 | return NULL; 120 | } 121 | 122 | result->merge(classInfo); 123 | result->mapFields(); 124 | if (fromObject != NULL) { 125 | result->setJavaObject(env, fromObject); 126 | } 127 | 128 | return result; 129 | } 130 | 131 | template 132 | TypeName* getNativeInstance(JNIEnv *env, jobject fromObject) { 133 | LOG_DEBUG("Getting native instance of class from registry"); 134 | TypeName *result = new TypeName(); 135 | const char *name = result->getCanonicalName(); 136 | if (name == NULL || strlen(name) == 0) { 137 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalArgumentException, 138 | "Could not find canonical name for class"); 139 | delete result; 140 | return NULL; 141 | } 142 | 143 | const TypeName *classInfo = dynamic_cast(get(name)); 144 | if (classInfo == NULL) { 145 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalStateException, 146 | "No class information registered for '%s'", name); 147 | delete result; 148 | return NULL; 149 | } 150 | 151 | if (classInfo->isInitialized()) { 152 | // Merge must be called so that cached fields information (namely for 153 | // the persisted long field pointer) can be found. 154 | result->merge(classInfo); 155 | JavaClass *instance = result->getPersistedInstance(env, fromObject); 156 | if (instance != NULL) { 157 | // Don't leak the result; we will instead return the object pointed to 158 | // by the persisted field pointer. 159 | delete result; 160 | return dynamic_cast(instance); 161 | } 162 | } 163 | 164 | return NULL; 165 | } 166 | 167 | /** 168 | * @brief Return number of items in the map 169 | * @return Number of items 170 | */ 171 | virtual const size_t size() const { 172 | return _classes.size(); 173 | } 174 | 175 | protected: 176 | ClassRegistryMap _classes; 177 | }; 178 | 179 | } // namespace jni 180 | } // namespace spotify 181 | 182 | #endif // __ClassRegistry_h__ 183 | -------------------------------------------------------------------------------- /src/main/cpp/JavaClassUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "JavaClassUtils.h" 23 | #include "JavaClass.h" 24 | #include "JavaExceptionUtils.h" 25 | #include 26 | #include 27 | 28 | namespace spotify { 29 | namespace jni { 30 | 31 | class JavaClassLoader : public JavaClass { 32 | public: 33 | JavaClassLoader(JNIEnv *env) : JavaClass(env) { initialize(env); } 34 | ~JavaClassLoader() {} 35 | 36 | const char* getCanonicalName() const { return kTypeJavaClass(ClassLoader); } 37 | 38 | void initialize(JNIEnv *env) { 39 | setClass(env); 40 | cacheMethod(env, "loadClass", kTypeJavaClass(Class), kTypeString, NULL); 41 | std::string signature; 42 | JavaClassUtils::makeSignature(signature, getCanonicalName(), NULL); 43 | methodGetSystemClassLoader = env->GetStaticMethodID(_clazz, "getSystemClassLoader", signature.c_str()); 44 | } 45 | 46 | void mapFields() {} 47 | 48 | jobject getClassLoader(JNIEnv *env) { 49 | jobject classLoader = env->CallStaticObjectMethod(_clazz, methodGetSystemClassLoader); 50 | JavaExceptionUtils::checkException(env); 51 | return classLoader; 52 | } 53 | 54 | jclass loadClass(JNIEnv *env, const char *class_name) { 55 | std::string binaryName = class_name; 56 | for (int i = 0; i < binaryName.length(); ++i) { 57 | if (binaryName[i] == '/') { 58 | binaryName[i] = '.'; 59 | } 60 | } 61 | 62 | LOG_DEBUG("Using ClassLoader to look up '%s'", binaryName.c_str()); 63 | JavaString className(binaryName); 64 | jobject classLoader = getClassLoader(env); 65 | jclass result = (jclass)env->CallObjectMethod(classLoader, getMethod("loadClass"), 66 | className.toJavaString(env).get()); 67 | JavaExceptionUtils::checkException(env); 68 | return result; 69 | } 70 | 71 | private: 72 | jmethodID methodGetSystemClassLoader; 73 | }; 74 | 75 | // Yes, I hate global static data as much as the next guy, however the alternative here would 76 | // be to keep the ClassLoader instance in the ClassRegistry. While that isn't such a bad idea, 77 | // it would require findClass() to be altered to take a ClassRegistry parameter, which looks a 78 | // bit weird. The ClassLoader is provided by the JVM and will live for the lifetime of the 79 | // application. 80 | static JavaClassLoader* sJavaClassLoader = NULL; 81 | 82 | void JavaClassUtils::setJavaClassLoader(JNIEnv *env) { 83 | LOG_DEBUG("Finding Java ClassLoader"); 84 | if (sJavaClassLoader == NULL) { 85 | sJavaClassLoader = new JavaClassLoader(env); 86 | } 87 | } 88 | 89 | jclass JavaClassUtils::findClass(JNIEnv *env, const char *class_name, bool useClassLoader) { 90 | jclass result = NULL; 91 | if (useClassLoader) { 92 | LOG_DEBUG("Finding class '%s' with thread ClassLoader", class_name); 93 | if (sJavaClassLoader == NULL) { 94 | LOG_DEBUG("Java ClassLoader is null, doing lazy initialization"); 95 | setJavaClassLoader(env); 96 | } 97 | 98 | jobject classLoader = sJavaClassLoader->getClassLoader(env); 99 | if (classLoader == NULL) { 100 | // If we can't get the ClassLoader then we're out of luck and must bail out... 101 | JavaExceptionUtils::throwExceptionOfType(env, kTypeIllegalStateException, 102 | "Could not find ClassLoader for thread"); 103 | return NULL; 104 | } 105 | 106 | result = sJavaClassLoader->loadClass(env, class_name); 107 | } else { 108 | LOG_DEBUG("Finding class '%s' with direct JNI call", class_name); 109 | result = env->FindClass(class_name); 110 | JavaExceptionUtils::checkException(env); 111 | } 112 | 113 | LOG_DEBUG("Lookup of class was %s", result != NULL ? "successful" : "unsuccessful"); 114 | if (result == NULL) { 115 | JavaExceptionUtils::throwExceptionOfType(env, kTypeJavaClass(NoClassDefFoundError), 116 | "Class '%s' not found (%s Java ClassLoader)", 117 | class_name, useClassLoader ? "using" : "not using"); 118 | } 119 | return result; 120 | } 121 | 122 | void JavaClassUtils::makeNameForSignature(std::string &receiver, const char *name) { 123 | if (name == NULL) { 124 | JavaExceptionUtils::throwExceptionOfType(JavaThreadUtils::getEnvForCurrentThread(), 125 | kTypeIllegalArgumentException, "Attempt to call makeNameForSignature with NULL name"); 126 | return; 127 | } else if (strlen(name) == 1) { 128 | // Primitive type, can be directly appended 129 | receiver = name; 130 | } else if (name[0] == '[') { 131 | if (strlen(name) == 2) { 132 | // Array of primitive types, again can be directly appended 133 | receiver = name; 134 | } else { 135 | if (name[1] == 'L' && name[strlen(name) - 1] == ';') { 136 | // Looks like this is already a proper signature 137 | receiver = name; 138 | } else { 139 | // Otherwise convert the class name to a proper JNI signature 140 | std::stringstream stream; 141 | stream << "[L" << name << ";"; 142 | receiver = stream.str(); 143 | } 144 | } 145 | } else { 146 | // Class names must be proceeded with an "L" and have a semicolon at the end, 147 | // however the canonical signatures provided in classes like JavaClass are 148 | // not expected to provide these. So check to see if this is a proper class 149 | // signature, and make one if not. 150 | if (name[0] == 'L' && name[strlen(name) - 1] == ';') { 151 | receiver = name; 152 | } else { 153 | std::stringstream stream; 154 | stream << "L" << name << ";"; 155 | receiver = stream.str(); 156 | } 157 | } 158 | } 159 | 160 | void JavaClassUtils::makeSignature(std::string &receiver, const char *return_type, ...) { 161 | va_list arguments; 162 | va_start(arguments, return_type); 163 | makeSignatureWithList(receiver, return_type, arguments); 164 | va_end(arguments); 165 | } 166 | 167 | void JavaClassUtils::makeSignatureWithList(std::string &receiver, const char *return_type, va_list arguments) { 168 | std::stringstream stringstream; 169 | stringstream << "("; 170 | char *argument; 171 | while ((argument = va_arg(arguments, char*)) != NULL) { 172 | std::string argumentSignature; 173 | makeNameForSignature(argumentSignature, argument); 174 | stringstream << argumentSignature; 175 | } 176 | stringstream << ")"; 177 | 178 | if (return_type == NULL) { 179 | stringstream << kTypeVoid; 180 | } else { 181 | std::string returnTypeSignature; 182 | makeNameForSignature(returnTypeSignature, return_type); 183 | stringstream << returnTypeSignature; 184 | } 185 | 186 | receiver = stringstream.str(); 187 | } 188 | 189 | } // namespace jni 190 | } // namespace spotify 191 | -------------------------------------------------------------------------------- /src/test/cpp/ClassRegistryTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Spotify AB 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | 22 | #include "ClassRegistryTest.h" 23 | #include "JUnitUtils.h" 24 | #include "TestObject.h" 25 | 26 | /* 27 | * NOTE: In this test class, JavaClass objects are created on the fly with the 28 | * constructor which takes JNIEnv*. Normally you should *not* do this; that ctor 29 | * is only meant to be called during initialization. However, the initialization 30 | * step isn't really present in these tests, so objects are created in this manner 31 | * simply to populate them with the correct class information. 32 | */ 33 | 34 | void ClassRegistryTest::initialize(JNIEnv *env) { 35 | setClass(env); 36 | 37 | addNativeMethod("createRegistry", (void*)createRegistry, kTypeVoid, NULL); 38 | 39 | addNativeMethod("addClass", (void*)addClass, kTypeVoid, NULL); 40 | addNativeMethod("addNullClass", (void*)addNullClass, kTypeVoid, NULL); 41 | addNativeMethod("addClassWithEmptyName", (void*)addClassWithEmptyName, kTypeVoid, NULL); 42 | addNativeMethod("addClassWithNullName", (void*)addClassWithNullName, kTypeVoid, NULL); 43 | addNativeMethod("addClassWithoutInfo", (void*)addClassWithoutInfo, kTypeVoid, NULL); 44 | addNativeMethod("addClassMultipleTimes", (void*)addClassMultipleTimes, kTypeVoid, NULL); 45 | 46 | addNativeMethod("get", (void*)get, kTypeVoid, NULL); 47 | addNativeMethod("getWithBracketOperator", (void*)getWithBracketOperator, kTypeVoid, NULL); 48 | addNativeMethod("getNullClass", (void*)getNullClass, kTypeVoid, NULL); 49 | addNativeMethod("getInvalidClass", (void*)getInvalidClass, kTypeVoid, NULL); 50 | 51 | TestObject testObject; 52 | addNativeMethod("nativeNewInstance", (void*)nativeNewInstance, kTypeVoid, testObject.getCanonicalName(), NULL); 53 | addNativeMethod("nativeNewInstanceWithNull", (void*)nativeNewInstanceWithNull, kTypeVoid, testObject.getCanonicalName(), NULL); 54 | 55 | registerNativeMethods(env); 56 | } 57 | 58 | void ClassRegistryTest::createRegistry(JNIEnv *env, jobject javaThis) { 59 | LOG_INFO("Starting test: createRegistry"); 60 | ClassRegistry registry; 61 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 62 | } 63 | 64 | void ClassRegistryTest::addClass(JNIEnv *env, jobject javaThis) { 65 | LOG_INFO("Starting test: addClass"); 66 | ClassRegistry registry; 67 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 68 | registry.add(env, new TestObject(env)); 69 | JUNIT_ASSERT_EQUALS_INT(1, registry.size()); 70 | } 71 | 72 | void ClassRegistryTest::addNullClass(JNIEnv *env, jobject javaThis) { 73 | LOG_INFO("Starting test: addNullClass"); 74 | ClassRegistry registry; 75 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 76 | registry.add(env, NULL); 77 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 78 | } 79 | 80 | void ClassRegistryTest::addClassWithEmptyName(JNIEnv *env, jobject javaThis) { 81 | LOG_INFO("Starting test: addClassWithEmptyName"); 82 | ClassRegistry registry; 83 | registry.add(env, new ClassWithName("")); 84 | } 85 | 86 | void ClassRegistryTest::addClassWithNullName(JNIEnv *env, jobject javaThis) { 87 | LOG_INFO("Starting test: addClassWithNullName"); 88 | ClassRegistry registry; 89 | registry.add(env, new ClassWithName(static_cast(NULL))); 90 | } 91 | 92 | void ClassRegistryTest::addClassWithoutInfo(JNIEnv *env, jobject javaThis) { 93 | LOG_INFO("Starting test: addClassWithoutInfo"); 94 | ClassRegistry registry; 95 | registry.add(env, new ClassWithName("invalid")); 96 | } 97 | 98 | void ClassRegistryTest::addClassMultipleTimes(JNIEnv *env, jobject javaThis) { 99 | LOG_INFO("Starting test: addClassMultipleTimes"); 100 | ClassRegistry registry; 101 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 102 | registry.add(env, new TestObject(env)); 103 | JUNIT_ASSERT_EQUALS_INT(1, registry.size()); 104 | registry.add(env, new TestObject(env)); 105 | // Should not be inserted multiple times 106 | JUNIT_ASSERT_EQUALS_INT(1, registry.size()); 107 | } 108 | 109 | void ClassRegistryTest::get(JNIEnv *env, jobject javaThis) { 110 | LOG_INFO("Starting test: get"); 111 | ClassRegistry registry; 112 | TestObject dummy; 113 | 114 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 115 | const TestObject *shouldBeNull = dynamic_cast(registry.get(dummy.getCanonicalName())); 116 | JUNIT_ASSERT_NULL(shouldBeNull); 117 | 118 | registry.add(env, new TestObject(env)); 119 | JUNIT_ASSERT_EQUALS_INT(1, registry.size()); 120 | const TestObject *result = dynamic_cast(registry.get(dummy.getCanonicalName())); 121 | JUNIT_ASSERT_NOT_NULL(result); 122 | // Sanity check just to make sure that the returned result matches our test object 123 | JUNIT_ASSERT_EQUALS_CSTRING(dummy.getCanonicalName(), result->getCanonicalName()); 124 | } 125 | 126 | void ClassRegistryTest::getWithBracketOperator(JNIEnv *env, jobject javaThis) { 127 | LOG_INFO("Starting test: getWithBracketOperator"); 128 | ClassRegistry registry; 129 | TestObject dummy; 130 | 131 | JUNIT_ASSERT_EQUALS_INT(0, registry.size()); 132 | const TestObject *shouldBeNull = dynamic_cast(registry[dummy.getCanonicalName()]); 133 | JUNIT_ASSERT_NULL(shouldBeNull); 134 | 135 | registry.add(env, new TestObject(env)); 136 | JUNIT_ASSERT_EQUALS_INT(1, registry.size()); 137 | const TestObject *result = dynamic_cast(registry[dummy.getCanonicalName()]); 138 | JUNIT_ASSERT_NOT_NULL(result); 139 | // Sanity check just to make sure that the returned result matches our test object 140 | JUNIT_ASSERT_EQUALS_CSTRING(dummy.getCanonicalName(), result->getCanonicalName()); 141 | } 142 | 143 | void ClassRegistryTest::getNullClass(JNIEnv *env, jobject javaThis) { 144 | LOG_INFO("Starting test: getNullClass"); 145 | ClassRegistry registry; 146 | // Should throw 147 | JUNIT_ASSERT_NULL(registry.get(NULL)); 148 | } 149 | 150 | void ClassRegistryTest::getInvalidClass(JNIEnv *env, jobject javaThis) { 151 | LOG_INFO("Starting test: getInvalidClass"); 152 | ClassRegistry registry; 153 | // Should throw 154 | JUNIT_ASSERT_NULL(registry.get("invalid")); 155 | } 156 | 157 | void ClassRegistryTest::nativeNewInstance(JNIEnv *env, jobject javaThis, jobject javaTestObject) { 158 | LOG_INFO("Starting test: nativeNewInstance"); 159 | ClassRegistry registry; 160 | registry.add(env, new TestObject(env)); 161 | TestObject *result = registry.newInstance(env, javaTestObject); 162 | JUNIT_ASSERT_NOT_NULL(result); 163 | JUNIT_ASSERT_EQUALS_INT(TEST_INTEGER, result->i); 164 | JUNIT_ASSERT_EQUALS_INT(TEST_SHORT, result->s); 165 | JUNIT_ASSERT_EQUALS_FLOAT(TEST_FLOAT, result->f, TEST_FLOAT_TOLERANCE); 166 | JUNIT_ASSERT_EQUALS_FLOAT(TEST_DOUBLE, result->d, TEST_FLOAT_TOLERANCE); 167 | JUNIT_ASSERT_EQUALS_BOOL(TEST_BOOLEAN, result->z); 168 | JUNIT_ASSERT_EQUALS_STRING(TEST_STRING, result->string.get()); 169 | JUNIT_ASSERT_EQUALS_INT(TEST_BYTE, result->b); 170 | #if HAS_RAW_STRING_LITERALS 171 | JUNIT_ASSERT_EQUALS_INT(TEST_CHAR, result->c); 172 | #endif 173 | } 174 | 175 | void ClassRegistryTest::nativeNewInstanceWithNull(JNIEnv *env, jobject javaThis, jobject javaTestObject) { 176 | LOG_INFO("Starting test: nativeNewInstanceWithNull"); 177 | ClassRegistry registry; 178 | registry.add(env, new TestObject(env)); 179 | TestObject *result = registry.newInstance(env, NULL); 180 | JUNIT_ASSERT_NOT_NULL(result); 181 | // Should have populated class info 182 | JUNIT_ASSERT_TRUE(result->isInitialized()); 183 | } 184 | --------------------------------------------------------------------------------