diff --git a/rcljava/CMakeLists.txt b/rcljava/CMakeLists.txt index 991af41a..6e30e56d 100644 --- a/rcljava/CMakeLists.txt +++ b/rcljava/CMakeLists.txt @@ -7,15 +7,17 @@ find_package(ament_cmake_export_jars REQUIRED) find_package(rcl REQUIRED) find_package(rmw REQUIRED) find_package(rmw_implementation_cmake REQUIRED) +find_package(rcljava_common REQUIRED) +find_package(JavaExtra MODULE) -if (ANDROID) +if(ANDROID) find_host_package(Java COMPONENTS Development) else() find_package(Java COMPONENTS Development) find_package(JNI REQUIRED) endif() -include (UseJava) +include(UseJava) set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.6" "-target" "1.6") @@ -23,6 +25,8 @@ if(NOT WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra") endif() +include_directories(include) + macro(target) if(NOT "${target_suffix} " STREQUAL " ") get_rcl_information("${rmw_implementation}" "rcl${target_suffix}") @@ -56,6 +60,7 @@ macro(target) ament_target_dependencies(${_target_name} "rcl${target_suffix}" + "rcljava_common" ) target_include_directories(${_target_name} @@ -63,30 +68,23 @@ macro(target) ${JNI_INCLUDE_DIRS} ) - if(NOT rosidl_generate_interfaces_SKIP_INSTALL) - ament_export_libraries(${_target_name}) + ament_export_libraries(${_target_name}) - install(TARGETS ${_msg_name}${_javaext_suffix} - DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}/${_msg_package_dir2}" - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - ) - install(TARGETS ${_target_name} - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - ) - endif() + install(TARGETS ${_target_name} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + ) endforeach() endmacro() set(${PROJECT_NAME}_sources + "src/main/java/org/ros2/rcljava/Consumer.java" "src/main/java/org/ros2/rcljava/Node.java" - "src/main/java/org/ros2/rcljava/RCLJava.java" "src/main/java/org/ros2/rcljava/Publisher.java" + "src/main/java/org/ros2/rcljava/RCLJava.java" "src/main/java/org/ros2/rcljava/Subscription.java" - "src/main/java/org/ros2/rcljava/Consumer.java" ) add_jar("${PROJECT_NAME}_jar" @@ -97,21 +95,50 @@ add_jar("${PROJECT_NAME}_jar" call_for_each_rmw_implementation(target) -if(AMENT_ENABLE_TESTING) +set(_install_jar_dir "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}") +get_property(_jar_file TARGET "${PROJECT_NAME}_jar" PROPERTY "JAR_FILE") + +install_jar("${PROJECT_NAME}_jar" "share/${PROJECT_NAME}/java") +ament_export_jars("share/${PROJECT_NAME}/java/${PROJECT_NAME}.jar") + +if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() - ament_add_nose_test(rcljavatests test - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + set(${PROJECT_NAME}_test_sources + "src/test/java/org/ros2/rcljava/NodeTest.java" + "src/test/java/org/ros2/rcljava/PublisherTest.java" + "src/test/java/org/ros2/rcljava/RCLJavaTest.java" + "src/test/java/org/ros2/rcljava/SubscriptionTest.java" + ) + + set(${PROJECT_NAME}_testsuites + "org.ros2.rcljava.NodeTest" + "org.ros2.rcljava.PublisherTest" + "org.ros2.rcljava.RCLJavaTest" + "org.ros2.rcljava.SubscriptionTest" ) -endif() -if(NOT rosidl_generate_interfaces_SKIP_INSTALL) - set(_install_jar_dir "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}") - get_property(_jar_file TARGET "${PROJECT_NAME}_jar" PROPERTY "JAR_FILE") + set(_deps_library_paths "") + foreach(_dep_lib ${std_msgs_LIBRARIES}) + get_filename_component(_dep_dir "${_dep_lib}" DIRECTORY) + list(APPEND _deps_library_paths ${_dep_dir}) + endforeach() + list(APPEND _deps_library_paths ${CMAKE_CURRENT_BINARY_DIR}) + + foreach(testsuite ${${PROJECT_NAME}_testsuites}) + add_junit_tests("${PROJECT_NAME}_tests_${testsuite}" + "${${PROJECT_NAME}_test_sources}" + TESTS + "${testsuite}" + INCLUDE_JARS + "${std_msgs_JARS}" + "${_jar_file}" + LIBRARY_PATHS + "${_deps_library_paths}" + ) + endforeach() - install_jar("${PROJECT_NAME}_jar" "share/${PROJECT_NAME}/java") - ament_export_jars("share/${PROJECT_NAME}/java/${PROJECT_NAME}.jar") endif() ament_package() diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_Node.h b/rcljava/include/org_ros2_rcljava_Node.h similarity index 50% rename from rcljava/src/main/cpp/org_ros2_rcljava_Node.h rename to rcljava/include/org_ros2_rcljava_Node.h index 68309700..e094e82d 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_Node.h +++ b/rcljava/include/org_ros2_rcljava_Node.h @@ -1,9 +1,23 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class org_ros2_rcljava_Node */ -#ifndef _Included_org_ros2_rcljava_Node -#define _Included_org_ros2_rcljava_Node +#ifndef ORG_ROS2_RCLJAVA_NODE_H_ +#define ORG_ROS2_RCLJAVA_NODE_H_ #ifdef __cplusplus extern "C" { #endif @@ -26,4 +40,4 @@ JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreateSubscriptionHandl #ifdef __cplusplus } #endif -#endif +#endif // ORG_ROS2_RCLJAVA_NODE_H_ diff --git a/rcljava/include/org_ros2_rcljava_Publisher.h b/rcljava/include/org_ros2_rcljava_Publisher.h new file mode 100644 index 00000000..716be087 --- /dev/null +++ b/rcljava/include/org_ros2_rcljava_Publisher.h @@ -0,0 +1,43 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_ros2_rcljava_Publisher */ + +#ifndef ORG_ROS2_RCLJAVA_PUBLISHER_H_ +#define ORG_ROS2_RCLJAVA_PUBLISHER_H_ +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_ros2_rcljava_Publisher + * Method: nativePublish + * Signature: (JLjava/lang/Object;) + */ +JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativePublish + (JNIEnv *, jclass, jlong, jobject); + +/* + * Class: org_ros2_rcljava_Node + * Method: nativeDispose + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativeDispose + (JNIEnv *, jclass, jlong, jlong); + +#ifdef __cplusplus +} +#endif +#endif // ORG_ROS2_RCLJAVA_PUBLISHER_H_ diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.h b/rcljava/include/org_ros2_rcljava_RCLJava.h similarity index 77% rename from rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.h rename to rcljava/include/org_ros2_rcljava_RCLJava.h index 14a72a9d..84bbd726 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.h +++ b/rcljava/include/org_ros2_rcljava_RCLJava.h @@ -1,9 +1,23 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class org_ros2_rcljava_RCLJava */ -#ifndef _Included_org_ros2_rcljava_RCLJava -#define _Included_org_ros2_rcljava_RCLJava +#ifndef ORG_ROS2_RCLJAVA_RCLJAVA_H_ +#define ORG_ROS2_RCLJAVA_RCLJAVA_H_ #ifdef __cplusplus extern "C" { #endif @@ -98,4 +112,4 @@ JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeShutdown #ifdef __cplusplus } #endif -#endif +#endif // ORG_ROS2_RCLJAVA_RCLJAVA_H_ diff --git a/rcljava/package.xml b/rcljava/package.xml index abcaac02..99b04df3 100644 --- a/rcljava/package.xml +++ b/rcljava/package.xml @@ -8,6 +8,8 @@ ament_cmake ament_cmake_export_jars + rcljava_common + rosidl_cmake rmw_implementation @@ -21,32 +23,9 @@ rcl - - - - - - - ament_cmake diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_Node.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_Node.cpp index 78450fc3..2b441625 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_Node.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_Node.cpp @@ -1,23 +1,43 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include "rmw/rmw.h" +#include "rcl/error_handling.h" +#include "rcl/rcl.h" +#include "rcl/node.h" +#include "rosidl_generator_c/message_type_support_struct.h" + +#include "rcljava_common/exceptions.h" +#include "rcljava_common/signatures.h" #include "org_ros2_rcljava_Node.h" -JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreatePublisherHandle - (JNIEnv *env, jclass, jlong node_handle, jclass jmessage_class, jstring jtopic) { +JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreatePublisherHandle(JNIEnv * env, jclass, + jlong node_handle, + jclass jmessage_class, + jstring jtopic) +{ jmethodID mid = env->GetStaticMethodID(jmessage_class, "getTypeSupport", "()J"); jlong jts = env->CallStaticLongMethod(jmessage_class, mid); - const char *topic_tmp = env->GetStringUTFChars(jtopic, 0); + const char * topic_tmp = env->GetStringUTFChars(jtopic, 0); std::string topic(topic_tmp); @@ -28,37 +48,33 @@ JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreatePublisherHandle rosidl_message_type_support_t * ts = reinterpret_cast(jts); - rcl_publisher_t * publisher = (rcl_publisher_t *)malloc(sizeof(rcl_publisher_t)); + rcl_publisher_t * publisher = static_cast(malloc(sizeof(rcl_publisher_t))); publisher->impl = NULL; rcl_publisher_options_t publisher_ops = rcl_publisher_get_default_options(); rcl_ret_t ret = rcl_publisher_init(publisher, node, ts, topic.c_str(), &publisher_ops); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to create publisher: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != NULL); - - env->ThrowNew(exception_class, message.c_str()); - - return -1; + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to create publisher: " + std::string(rcl_get_error_string_safe())); + return 0; } jlong jpublisher = reinterpret_cast(publisher); - return jpublisher; } -JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreateSubscriptionHandle - (JNIEnv *env, jclass, jlong node_handle, jclass jmessage_class, jstring jtopic) { +JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreateSubscriptionHandle(JNIEnv * env, + jclass, + jlong node_handle, + jclass jmessage_class, + jstring jtopic) +{ jmethodID mid = env->GetStaticMethodID(jmessage_class, "getTypeSupport", "()J"); jlong jts = env->CallStaticLongMethod(jmessage_class, mid); - const char *topic_tmp = env->GetStringUTFChars(jtopic, 0); + const char * topic_tmp = env->GetStringUTFChars(jtopic, 0); std::string topic(topic_tmp); @@ -69,27 +85,20 @@ JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_Node_nativeCreateSubscriptionHandl rosidl_message_type_support_t * ts = reinterpret_cast(jts); - rcl_subscription_t * subscription = (rcl_subscription_t *)malloc(sizeof(rcl_subscription_t)); + rcl_subscription_t * subscription = + static_cast(malloc(sizeof(rcl_subscription_t))); subscription->impl = NULL; rcl_subscription_options_t subscription_ops = rcl_subscription_get_default_options(); rcl_ret_t ret = rcl_subscription_init(subscription, node, ts, topic.c_str(), &subscription_ops); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to create publisher: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != NULL); - - env->ThrowNew(exception_class, message.c_str()); - - return -1; + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to create subscription: " + std::string(rcl_get_error_string_safe())); + return 0; } jlong jsubscription = reinterpret_cast(subscription); - return jsubscription; } diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.cpp index 437f0dcc..4f6687d5 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.cpp @@ -1,20 +1,38 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + #include #include #include #include -#include -#include -#include -#include -#include -#include +#include "rmw/rmw.h" +#include "rcl/error_handling.h" +#include "rcl/rcl.h" +#include "rcl/node.h" -#include "org_ros2_rcljava_Publisher.h" +#include "rcljava_common/exceptions.h" +#include "rcljava_common/signatures.h" -JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativePublish - (JNIEnv *env, jclass, jlong publisher_handle, jobject jmsg) { +#include "org_ros2_rcljava_Publisher.h" +JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativePublish(JNIEnv * env, jclass, + jlong publisher_handle, + jobject jmsg) +{ rcl_publisher_t * publisher = reinterpret_cast(publisher_handle); jclass jmessage_class = env->GetObjectClass(jmsg); @@ -22,7 +40,6 @@ JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativePublish jmethodID mid = env->GetStaticMethodID(jmessage_class, "getFromJavaConverter", "()J"); jlong jfrom_java_converter = env->CallStaticLongMethod(jmessage_class, mid); - using convert_from_java_signature = void * (*)(jobject, void *); convert_from_java_signature convert_from_java = reinterpret_cast(jfrom_java_converter); @@ -30,21 +47,16 @@ JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativePublish rcl_ret_t ret = rcl_publish(publisher, raw_ros_message); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to publish: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != NULL); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to publish: " + std::string(rcl_get_error_string_safe())); } } -JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativeDispose - (JNIEnv *env, jclass, jlong node_handle, jlong publisher_handle) { - +JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativeDispose(JNIEnv * env, jclass, + jlong node_handle, + jlong publisher_handle) +{ rcl_node_t * node = reinterpret_cast(node_handle); assert(node != NULL); @@ -56,14 +68,8 @@ JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativeDispose rcl_ret_t ret = rcl_publisher_fini(publisher, node); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to destroy publisher: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != NULL); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to destroy publisher: " + std::string(rcl_get_error_string_safe())); } } diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.h b/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.h deleted file mode 100644 index c87849fd..00000000 --- a/rcljava/src/main/cpp/org_ros2_rcljava_Publisher.h +++ /dev/null @@ -1,29 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_ros2_rcljava_Publisher */ - -#ifndef _Included_org_ros2_rcljava_Publisher -#define _Included_org_ros2_rcljava_Publisher -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_ros2_rcljava_Publisher - * Method: nativePublish - * Signature: (JLjava/lang/Object;) - */ -JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativePublish - (JNIEnv *, jclass, jlong, jobject); - -/* - * Class: org_ros2_rcljava_Node - * Method: nativeDispose - * Signature: (JJ)V - */ -JNIEXPORT void JNICALL Java_org_ros2_rcljava_Publisher_nativeDispose - (JNIEnv *, jclass, jlong, jlong); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp index 4161ce22..34fe4804 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp @@ -1,77 +1,84 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include -#include + #include -#include -#include -#include -#include -#include #include +#include + +#include "rmw/rmw.h" +#include "rcl/error_handling.h" +#include "rcl/rcl.h" +#include "rcl/node.h" +#include "rosidl_generator_c/message_type_support.h" + +#include "rcljava_common/exceptions.h" +#include "rcljava_common/signatures.h" #include "org_ros2_rcljava_RCLJava.h" -JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeRCLJavaInit - (JNIEnv *env, jclass) { +JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeRCLJavaInit(JNIEnv * env, jclass) +{ // TODO(esteve): parse args rcl_ret_t ret = rcl_init(0, nullptr, rcl_get_default_allocator()); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to init: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to init: " + std::string(rcl_get_error_string_safe())); } } -JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeCreateNodeHandle - (JNIEnv *env, jclass, jstring jnode_name) { - +JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeCreateNodeHandle(JNIEnv * env, jclass, + jstring jnode_name) +{ const char * node_name_tmp = env->GetStringUTFChars(jnode_name, 0); std::string node_name(node_name_tmp); env->ReleaseStringUTFChars(jnode_name, node_name_tmp); - rcl_node_t * node = (rcl_node_t *)malloc(sizeof(rcl_node_t)); + rcl_node_t * node = static_cast(malloc(sizeof(rcl_node_t))); node->impl = nullptr; rcl_node_options_t default_options = rcl_node_get_default_options(); rcl_ret_t ret = rcl_node_init(node, node_name.c_str(), &default_options); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to create node: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); - - return -1; + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to create node: " + std::string(rcl_get_error_string_safe())); + return 0; } jlong node_handle = reinterpret_cast(node); return node_handle; } -JNIEXPORT jstring JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetRMWIdentifier - (JNIEnv *env, jclass) { +JNIEXPORT jstring JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetRMWIdentifier(JNIEnv * env, jclass) +{ const char * rmw_implementation_identifier = rmw_get_implementation_identifier(); return env->NewStringUTF(rmw_implementation_identifier); } -JNIEXPORT jboolean JNICALL Java_org_ros2_rcljava_RCLJava_nativeOk - (JNIEnv *, jclass) { +JNIEXPORT jboolean JNICALL Java_org_ros2_rcljava_RCLJava_nativeOk(JNIEnv *, jclass) +{ return rcl_ok(); } -JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetZeroInitializedWaitSet - (JNIEnv *env, jclass) { - rcl_wait_set_t * wait_set = (rcl_wait_set_t *)malloc(sizeof(rcl_wait_set_t)); +JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetZeroInitializedWaitSet(JNIEnv * env, + jclass) +{ + rcl_wait_set_t * wait_set = static_cast(malloc(sizeof(rcl_wait_set_t))); wait_set->subscriptions = nullptr; wait_set->size_of_subscriptions = 0; wait_set->guard_conditions = nullptr; @@ -87,9 +94,11 @@ JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetZeroInitializedWa return wait_set_handle; } -JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetInit - (JNIEnv *env, jclass, jlong wait_set_handle, jint number_of_subscriptions, - jint number_of_guard_conditions, jint number_of_timers) { +JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetInit(JNIEnv * env, jclass, + jlong wait_set_handle, + jint number_of_subscriptions, + jint number_of_guard_conditions, jint number_of_timers) +{ jint number_of_clients = 0; jint number_of_services = 0; @@ -99,81 +108,61 @@ JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetInit wait_set, number_of_subscriptions, number_of_guard_conditions, number_of_timers, number_of_clients, number_of_services, rcl_get_default_allocator()); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to initialize wait set: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to initialize wait set: " + std::string(rcl_get_error_string_safe())); } } -JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetClearSubscriptions - (JNIEnv *env, jclass, jlong wait_set_handle) { - +JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetClearSubscriptions(JNIEnv * env, + jclass, + jlong wait_set_handle) +{ rcl_wait_set_t * wait_set = reinterpret_cast(wait_set_handle); rcl_ret_t ret = rcl_wait_set_clear_subscriptions(wait_set); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to clear subscriptions from wait set: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to clear subscriptions from wait set: " + std::string(rcl_get_error_string_safe())); } } -JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetAddSubscription - (JNIEnv *env, jclass, jlong wait_set_handle, jlong subscription_handle) { - +JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWaitSetAddSubscription(JNIEnv * env, + jclass, + jlong wait_set_handle, + jlong subscription_handle) +{ rcl_wait_set_t * wait_set = reinterpret_cast(wait_set_handle); rcl_subscription_t * subscription = reinterpret_cast(subscription_handle); rcl_ret_t ret = rcl_wait_set_add_subscription(wait_set, subscription); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to add subscription to wait set: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to add subscription to wait set: " + std::string(rcl_get_error_string_safe())); } } -JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWait - (JNIEnv *env, jclass, jlong wait_set_handle) { +JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeWait(JNIEnv * env, jclass, + jlong wait_set_handle) +{ rcl_wait_set_t * wait_set = reinterpret_cast(wait_set_handle); rcl_ret_t ret = rcl_wait(wait_set, RCL_S_TO_NS(1)); if (ret != RCL_RET_OK && ret != RCL_RET_TIMEOUT) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to wait on wait set: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to wait on wait set: " + std::string(rcl_get_error_string_safe())); } } -JNIEXPORT jobject JNICALL Java_org_ros2_rcljava_RCLJava_nativeTake - (JNIEnv *env, jclass, jlong subscription_handle, jclass jmessage_class) { - +JNIEXPORT jobject JNICALL Java_org_ros2_rcljava_RCLJava_nativeTake(JNIEnv * env, jclass, + jlong subscription_handle, + jclass jmessage_class) +{ rcl_subscription_t * subscription = reinterpret_cast(subscription_handle); jmethodID jfrom_mid = env->GetStaticMethodID(jmessage_class, "getFromJavaConverter", "()J"); jlong jfrom_java_converter = env->CallStaticLongMethod(jmessage_class, jfrom_mid); - using convert_from_java_signature = void * (*)(jobject, void *); convert_from_java_signature convert_from_java = reinterpret_cast(jfrom_java_converter); @@ -185,22 +174,16 @@ JNIEXPORT jobject JNICALL Java_org_ros2_rcljava_RCLJava_nativeTake rcl_ret_t ret = rcl_take(subscription, taken_msg, nullptr); if (ret != RCL_RET_OK && ret != RCL_RET_SUBSCRIPTION_TAKE_FAILED) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to take from a subscription: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to take from a subscription: " + std::string(rcl_get_error_string_safe())); + return nullptr; } if (ret != RCL_RET_SUBSCRIPTION_TAKE_FAILED) { jmethodID jto_mid = env->GetStaticMethodID(jmessage_class, "getToJavaConverter", "()J"); jlong jto_java_converter = env->CallStaticLongMethod(jmessage_class, jto_mid); - using convert_to_java_signature = jobject (*)(void *, jobject); convert_to_java_signature convert_to_java = reinterpret_cast(jto_java_converter); @@ -212,18 +195,12 @@ JNIEXPORT jobject JNICALL Java_org_ros2_rcljava_RCLJava_nativeTake return nullptr; } -JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeShutdown - (JNIEnv *env, jclass) { +JNIEXPORT void JNICALL Java_org_ros2_rcljava_RCLJava_nativeShutdown(JNIEnv * env, jclass) +{ rcl_ret_t ret = rcl_shutdown(); if (ret != RCL_RET_OK) { - jclass exception_class; - const char *class_name = "java/lang/IllegalStateException"; - std::string message("Failed to shutdown: " + std::string(rcl_get_error_string_safe())); - - exception_class = env->FindClass(class_name); - - assert(exception_class != nullptr); - - env->ThrowNew(exception_class, message.c_str()); + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "Failed to shutdown: " + std::string(rcl_get_error_string_safe())); } } diff --git a/rcljava/src/main/java/org/ros2/rcljava/Consumer.java b/rcljava/src/main/java/org/ros2/rcljava/Consumer.java index 6c240a42..89eca567 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/Consumer.java +++ b/rcljava/src/main/java/org/ros2/rcljava/Consumer.java @@ -12,8 +12,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.ros2.rcljava; +/** + * This is a copy of {@link java.util.funcion.Consumer} for platforms that don't + * support Java 8's API (e.g. Android < 7.0) + * + * @param - the type of the input to the operation + */ public interface Consumer { - void accept(T t); + /** + * Performs this operation on the given argument. + * + * @param input - the input argument + */ + void accept(T input); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/Node.java b/rcljava/src/main/java/org/ros2/rcljava/Node.java index 2b4c8478..8aa0b9ff 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/Node.java +++ b/rcljava/src/main/java/org/ros2/rcljava/Node.java @@ -12,52 +12,158 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ros2.rcljava; -import java.lang.ref.WeakReference; +package org.ros2.rcljava; -import java.util.ArrayList; -import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; +/** + * This class serves as a bridge between ROS2's rcl_node_t and RCLJava. + * A Node must be created via @{link RCLJava#createNode(String)} + */ public class Node { - static { - try { - System.loadLibrary("rcljavaNode__" + RCLJava.getRMWIdentifier()); - } catch (UnsatisfiedLinkError e) { - System.err.println("Native code library failed to load.\n" + e); - System.exit(1); - } + static { + try { + System.loadLibrary("rcljavaNode__" + RCLJava.getRMWIdentifier()); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Native code library failed to load.\n" + ule); + System.exit(1); } + } - private long nodeHandle; - private List subscriptions = new ArrayList(); + /** + * An integer that represents a pointer to the underlying ROS2 node + * structure (rcl_node_t). + */ + private final long nodeHandle; - public Node(long nodeHandle) { - this.nodeHandle = nodeHandle; - } + /** + * All the @{link Subscription}s that have been created through this instance. + */ + private final Queue subscriptions; - private static native long nativeCreatePublisherHandle( - long nodeHandle, Class cls, String topic); + /** + * All the @{link Publisher}s that have been created through this instance. + */ + private final Queue publishers; - private static native long nativeCreateSubscriptionHandle( - long nodeHandle, Class cls, String topic); + /** + * Constructor. + * + * @param nodeHandle A pointer to the underlying ROS2 node structure. Must not + * be zero. + */ + public Node(final long nodeHandle) { + this.nodeHandle = nodeHandle; + this.publishers = new LinkedBlockingQueue(); + this.subscriptions = new LinkedBlockingQueue(); + } - public Publisher createPublisher(Class cls, String topic) { - long publisherHandle = nativeCreatePublisherHandle(this.nodeHandle, cls, topic); - Publisher publisher = new Publisher(this.nodeHandle, publisherHandle); - RCLJava.publisherReferences.add(new WeakReference(publisher)); - return publisher; - } + /** + * Create a ROS2 publisher (rcl_publisher_t) and return a pointer to + * it as an integer. + * + * @param The type of the messages that will be published by the + * created @{link Publisher}. + * @param nodeHandle A pointer to the underlying ROS2 node structure. + * @param messageType The class of the messages that will be published by the + * created @{link Publisher}. + * @param topic The topic to which the created @{link Publisher} will + * publish messages. + * @return A pointer to the underlying ROS2 publisher structure. + */ + private static native long nativeCreatePublisherHandle( + long nodeHandle, Class messageType, String topic); - public Subscription createSubscription(Class cls, String topic, Consumer callback) { - long subscriptionHandle = nativeCreateSubscriptionHandle(this.nodeHandle, cls, topic); + /** + * Create a ROS2 subscription (rcl_subscription_t) and return a pointer to + * it as an integer. + * + * @param The type of the messages that will be received by the + * created @{link Subscription}. + * @param nodeHandle A pointer to the underlying ROS2 node structure. + * @param messageType The class of the messages that will be received by the + * created @{link Subscription}. + * @param topic The topic from which the created @{link Subscription} will + * receive messages. + * @return A pointer to the underlying ROS2 subscription structure. + */ + private static native long nativeCreateSubscriptionHandle( + long nodeHandle, Class messageType, String topic); - Subscription subscription = new Subscription(this.nodeHandle, subscriptionHandle, cls, topic, callback); - this.subscriptions.add(subscription); - return subscription; - } + /** + * Create a Publisher<T>. + * + * @param The type of the messages that will be published by the + * created @{link Publisher}. + * @param messageType The class of the messages that will be published by the + * created @{link Publisher}. + * @param topic The topic to which the created @{link Publisher} will + * publish messages. + * @return A @{link Publisher} that represents the underlying ROS2 publisher + * structure. + */ + public final Publisher createPublisher( + final Class messageType, final String topic) { + long publisherHandle = nativeCreatePublisherHandle(this.nodeHandle, + messageType, topic); + Publisher publisher = new Publisher(this.nodeHandle, + publisherHandle, topic); + this.publishers.add(publisher); + return publisher; + } - public List getSubscriptions() { - return this.subscriptions; - } + /** + * Create a Subscription<T>. + * + * @param The type of the messages that will be received by the + * created @{link Subscription}. + * @param messageType The class of the messages that will be received by the + * created @{link Subscription}. + * @param topic The topic from which the created @{link Subscription} will + * receive messages. + * @param callback The callback function that will be triggered when a + * message is received by the @{link Subscription}. + * @return A @{link Subscription} that represents the underlying ROS2 + * subscription structure. + */ + public final Subscription createSubscription( + final Class messageType, final String topic, + final Consumer callback) { + + long subscriptionHandle = nativeCreateSubscriptionHandle( + this.nodeHandle, messageType, topic); + + Subscription subscription = new Subscription( + this.nodeHandle, subscriptionHandle, messageType, topic, callback); + + this.subscriptions.add(subscription); + return subscription; + } + + /** + * @return All the @{link Subscription}s that were created by this instance. + */ + public final Queue getSubscriptions() { + return this.subscriptions; + } + + /** + * @return All the @{link Publisher}s that were created by this instance. + */ + public final Queue getPublishers() { + return this.publishers; + } + + /** + * Safely destroy the underlying ROS2 node structure. + */ + public final void dispose() { + // TODO(esteve): implement + } + + public final long getNodeHandle() { + return this.nodeHandle; + } } diff --git a/rcljava/src/main/java/org/ros2/rcljava/Publisher.java b/rcljava/src/main/java/org/ros2/rcljava/Publisher.java index c964d509..a64ede4a 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/Publisher.java +++ b/rcljava/src/main/java/org/ros2/rcljava/Publisher.java @@ -12,36 +12,102 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.ros2.rcljava; +/** + * This class serves as a bridge between ROS2's rcl_publisher_t and RCLJava. + * A Publisher must be created via + * @{link Node#createPublisher(Class<T>, String)} + * + * @param The type of the messages that this publisher will publish. + */ public class Publisher { - static { - try { - System.loadLibrary("rcljavaPublisher__" + RCLJava.getRMWIdentifier()); - } catch (UnsatisfiedLinkError e) { - System.err.println("Native code library failed to load.\n" + e); - System.exit(1); - } + static { + try { + System.loadLibrary("rcljavaPublisher__" + RCLJava.getRMWIdentifier()); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Native code library failed to load.\n" + ule); + System.exit(1); } + } - private long nodeHandle; - private long publisherHandle; + /** + * An integer that represents a pointer to the underlying ROS2 node + * structure (rcl_node_t). + */ + private final long nodeHandle; - public Publisher(long nodeHandle, long publisherHandle) { - this.nodeHandle = nodeHandle; - this.publisherHandle = publisherHandle; - } + /** + * An integer that represents a pointer to the underlying ROS2 publisher + * structure (rcl_publisher_t). + */ + private final long publisherHandle; - private static native void nativePublish(long publisherHandle, T msg); + /** + * The topic to which this publisher will publish messages. + */ + private final String topic; - public void publish(T msg) { - nativePublish(this.publisherHandle, msg); - } + /** + * Constructor. + * + * @param nodeHandle A pointer to the underlying ROS2 node structure that + * created this subscription, as an integer. Must not be zero. + * @param publisherHandle A pointer to the underlying ROS2 publisher + * structure, as an integer. Must not be zero. + * @param topic The topic to which this publisher will publish messages. + */ + public Publisher(final long nodeHandle, final long publisherHandle, + final String topic) { + this.nodeHandle = nodeHandle; + this.publisherHandle = publisherHandle; + this.topic = topic; + } - private static native void nativeDispose( - long nodeHandle, long publisherHandle); + /** + * Publish a message via the underlying ROS2 mechanisms. + * + * @param The type of the messages that this publisher will publish. + * @param publisherHandle A pointer to the underlying ROS2 publisher + * structure, as an integer. Must not be zero. + * @param message An instance of the <T> parameter. + */ + private static native void nativePublish( + long publisherHandle, T message); - public void dispose() { - nativeDispose(this.nodeHandle, this.publisherHandle); - } + /** + * Publish a message. + * + * @param message An instance of the <T> parameter. + */ + public final void publish(final T message) { + nativePublish(this.publisherHandle, message); + } + + /** + * Destroy a ROS2 publisher (rcl_publisher_t). + * + * @param nodeHandle A pointer to the underlying ROS2 node structure that + * created this subscription, as an integer. Must not be zero. + * @param publisherHandle A pointer to the underlying ROS2 publisher + * structure, as an integer. Must not be zero. + */ + private static native void nativeDispose( + long nodeHandle, long publisherHandle); + + /** + * Safely destroy the underlying ROS2 publisher structure. + */ + public final void dispose() { + nativeDispose(this.nodeHandle, this.publisherHandle); + } + + public final long getNodeHandle() { + return this.nodeHandle; + } + + public final long getPublisherHandle() { + return this.publisherHandle; + } } diff --git a/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java b/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java index 9a67c376..9874b0a0 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java +++ b/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java @@ -12,138 +12,216 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ros2.rcljava; -import java.lang.ref.WeakReference; +package org.ros2.rcljava; +import java.util.Map; import java.util.Queue; -import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.LinkedBlockingQueue; -import java.util.Map; -import java.util.concurrent.ConcurrentSkipListMap; +/** + * Entry point for the ROS2 Java API, similar to the rclcpp API. + */ +public final class RCLJava { + /** + * Private constructor so this cannot be instantiated. + */ + private RCLJava() { } -public class RCLJava { - public static Queue publisherReferences = new LinkedBlockingQueue(); - - static { - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - for(WeakReference publisherReference : publisherReferences) { - if(publisherReference.get() != null) { - publisherReference.get().dispose(); - } - } - } - }); - } + /** + * All the @{link Node}s that have been created. + */ + private static Queue nodes; - private static String rmwImplementation = null; - private static boolean initialized = false; + static { + nodes = new LinkedBlockingQueue(); - private static final Map rmwToTypesupport = new ConcurrentSkipListMap() {{ + RMW_TO_TYPESUPPORT = new ConcurrentSkipListMap() {{ put("rmw_fastrtps_cpp", "rosidl_typesupport_introspection_c"); put("rmw_opensplice_cpp", "rosidl_typesupport_opensplice_c"); put("rmw_connext_cpp", "rosidl_typesupport_connext_c"); put("rmw_connext_dynamic_cpp", "rosidl_typesupport_introspection_c"); - }}; - - public static void rclJavaInit() { - synchronized(RCLJava.class) { - if (!initialized) { - if (RCLJava.rmwImplementation == null) { - for(Map.Entry entry : rmwToTypesupport.entrySet()) { - try { - setRMWImplementation(entry.getKey()); - break; - } catch(UnsatisfiedLinkError ule) { - // TODO(esteve): handle exception - } catch(Exception e) { - // TODO(esteve): handle exception - } - } - } - if (RCLJava.rmwImplementation == null) { - System.err.println("No RMW implementation found"); - System.exit(1); - } else { - nativeRCLJavaInit(); - initialized = true; - } + } + }; + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + for (Node node : nodes) { + node.dispose(); + } + } + }); + } + + /** + * The identifier of the currently active RMW implementation. + */ + private static String rmwImplementation = null; + + /** + * Flag to indicate if RCLJava has been fully initialized, with a valid RMW + * implementation. + */ + private static boolean initialized = false; + + /** + * A mapping between RMW implementations and their typesupports. + */ + private static final Map RMW_TO_TYPESUPPORT; + + /** + * @return true if RCLJava has been fully initialized, false otherwise. + */ + public static boolean isInitialized() { + return RCLJava.initialized; + } + + /** + * Initialize the RCLJava API. If successful, a valid RMW implementation will + * be loaded and accessible, enabling the creating of ROS2 entities + * (@{link Node}s, @{link Publisher}s and @{link Subscription}s. + */ + public static void rclJavaInit() { + synchronized (RCLJava.class) { + if (!initialized) { + if (RCLJava.rmwImplementation == null) { + for (Map.Entry entry + : RMW_TO_TYPESUPPORT.entrySet()) { + + try { + setRMWImplementation(entry.getKey()); + break; + } catch (UnsatisfiedLinkError ule) { + // TODO(esteve): handle exception + } catch (Exception exc) { + // TODO(esteve): handle exception } + } } - } - - private static native void nativeRCLJavaInit(); - - private static native long nativeCreateNodeHandle(String nodeName); - - public static String getTypesupportIdentifier() { - String typesupportIdentifier = rmwToTypesupport.get(nativeGetRMWIdentifier()); - return typesupportIdentifier; - } - - public static void setRMWImplementation(String rmwImplementation) throws Exception { - synchronized(RCLJava.class) { - System.loadLibrary("rcljavaRCLJava__" + rmwImplementation); - RCLJava.rmwImplementation = rmwImplementation; + if (RCLJava.rmwImplementation == null) { + System.err.println("No RMW implementation found"); + System.exit(1); + } else { + nativeRCLJavaInit(); + initialized = true; } + } } - - private static native String nativeGetRMWIdentifier(); - - public static String getRMWIdentifier() { - return nativeGetRMWIdentifier(); - } - - private static native boolean nativeOk(); - - public static boolean ok() { - return nativeOk(); + } + + /** + * Initialize the underlying rcl layer. + */ + private static native void nativeRCLJavaInit(); + + /** + * Create a ROS2 node (rcl_node_t) and return a pointer to it as an integer. + * + * @param nodeName The name that will identify this node in a ROS2 graph. + * @return A pointer to the underlying ROS2 node structure. + */ + private static native long nativeCreateNodeHandle(String nodeName); + + public static String getTypesupportIdentifier() { + return RMW_TO_TYPESUPPORT.get(nativeGetRMWIdentifier()); + } + + public static void setRMWImplementation( + final String rmwImplementation) throws Exception { + + synchronized (RCLJava.class) { + System.loadLibrary("rcljavaRCLJava__" + rmwImplementation); + RCLJava.rmwImplementation = rmwImplementation; } - - public static Node createNode(String nodeName) { - long nodeHandle = nativeCreateNodeHandle(nodeName); - Node node = new Node(nodeHandle); - return node; + } + + /** + * @return The identifier of the currently active RMW implementation via the + * native ROS2 API. + */ + private static native String nativeGetRMWIdentifier(); + + /** + * @return The identifier of the currently active RMW implementation. + */ + public static String getRMWIdentifier() { + return nativeGetRMWIdentifier(); + } + + /** + * Call the underlying ROS2 rcl mechanism to check if ROS2 has been shut + * down. + * + * @return true if RCLJava hasn't been shut down, false otherwise. + */ + private static native boolean nativeOk(); + + /** + * @return true if RCLJava hasn't been shut down, false otherwise. + */ + public static boolean ok() { + return nativeOk(); + } + + /** + * Create a @{link Node}. + * + * @param nodeName The name that will identify this node in a ROS2 graph. + * @return A @{link Node} that represents the underlying ROS2 node + * structure. + */ + public static Node createNode(final String nodeName) { + long nodeHandle = nativeCreateNodeHandle(nodeName); + Node node = new Node(nodeHandle); + nodes.add(node); + return node; + } + + public static void spinOnce(final Node node) { + long waitSetHandle = nativeGetZeroInitializedWaitSet(); + + nativeWaitSetInit(waitSetHandle, node.getSubscriptions().size(), 0, 0); + + nativeWaitSetClearSubscriptions(waitSetHandle); + + for (Subscription subscription : node.getSubscriptions()) { + nativeWaitSetAddSubscription( + waitSetHandle, subscription.getSubscriptionHandle()); } - public static void spinOnce(Node node) { - long waitSetHandle = nativeGetZeroInitializedWaitSet(); - - nativeWaitSetInit(waitSetHandle, node.getSubscriptions().size(), 0, 0); - - nativeWaitSetClearSubscriptions(waitSetHandle); + nativeWait(waitSetHandle); - for(Subscription subscription : node.getSubscriptions()) { - nativeWaitSetAddSubscription(waitSetHandle, subscription.getSubscriptionHandle()); - } - - nativeWait(waitSetHandle); - - for(Subscription subscription : node.getSubscriptions()) { - Object msg = nativeTake(subscription.getSubscriptionHandle(), subscription.getMsgType()); - if (msg != null) { - subscription.getCallback().accept(msg); - } - } + for (Subscription subscription : node.getSubscriptions()) { + Object message = nativeTake( + subscription.getSubscriptionHandle(), + subscription.getMessageType()); + if (message != null) { + subscription.getCallback().accept(message); + } } + } - private static native void nativeShutdown(); + private static native void nativeShutdown(); - public static void shutdown() { - nativeShutdown(); - } + public static void shutdown() { + nativeShutdown(); + } - private static native long nativeGetZeroInitializedWaitSet(); + private static native long nativeGetZeroInitializedWaitSet(); - private static native void nativeWaitSetInit(long waitSetHandle, int numberOfSubscriptions, int numberOfGuardConditions, int numberOfTimers); + private static native void nativeWaitSetInit( + long waitSetHandle, int numberOfSubscriptions, + int numberOfGuardConditions, int numberOfTimers); - private static native void nativeWaitSetClearSubscriptions(long waitSetHandle); + private static native void nativeWaitSetClearSubscriptions( + long waitSetHandle); - private static native void nativeWaitSetAddSubscription(long waitSetHandle, long subscriptionHandle); + private static native void nativeWaitSetAddSubscription( + long waitSetHandle, long subscriptionHandle); - private static native void nativeWait(long waitSetHandle); + private static native void nativeWait(long waitSetHandle); - private static native Object nativeTake(long SubscriptionHandle, Class msgType); + private static native Object nativeTake(long subscriptionHandle, + Class messageType); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/Subscription.java b/rcljava/src/main/java/org/ros2/rcljava/Subscription.java index a40d2ecb..1f593494 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/Subscription.java +++ b/rcljava/src/main/java/org/ros2/rcljava/Subscription.java @@ -12,33 +12,94 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.ros2.rcljava; +/** + * This class serves as a bridge between ROS2's rcl_subscription_t and RCLJava. + * A Subscription must be created via + * @{link Node#createSubscription(Class<T>, String, Consumer<T>)} + * + * @param The type of the messages that this subscription will receive. + */ public class Subscription { - private long nodeHandle; - private long subscriptionHandle; - private Class msgType; - private String topic; - private Consumer callback; - - public Subscription(long nodeHandle, long subscriptionHandle, Class msgType, String topic, Consumer callback) { - this.nodeHandle = nodeHandle; - this.subscriptionHandle = subscriptionHandle; - this.msgType = msgType; - this.topic = topic; - this.callback = callback; - } - - public Consumer getCallback() { - return callback; - } - - public Class getMsgType() { - return msgType; - } - - public long getSubscriptionHandle() { - return subscriptionHandle; - } + /** + * An integer that represents a pointer to the underlying ROS2 node + * structure (rcl_node_t). + */ + private final long nodeHandle; + + /** + * An integer that represents a pointer to the underlying ROS2 subscription + * structure (rcl_subsription_t). + */ + private final long subscriptionHandle; + + /** + * The class of the messages that this subscription may receive. + */ + private final Class messageType; + + /** + * The topic to which this subscription is subscribed. + */ + private final String topic; + + /** + * The callback function that will be triggered when a new message is + * received. + */ + private final Consumer callback; + + /** + * Constructor. + * + * @param nodeHandle A pointer to the underlying ROS2 node structure that + * created this subscription, as an integer. Must not be zero. + * @param subscriptionHandle A pointer to the underlying ROS2 subscription + * structure, as an integer. Must not be zero. + * @param messageType The Class of the messages that this + * subscription will receive. We need this because of Java's type erasure, + * which doesn't allow us to use the generic parameter of + * @{link org.ros2.rcljava.Subscription} directly. + * @param topic The topic to which this subscription will be subscribed. + * @param callback The callback function that will be triggered when a new + * message is received. + */ + public Subscription(final long nodeHandle, final long subscriptionHandle, + final Class messageType, final String topic, + final Consumer callback) { + this.nodeHandle = nodeHandle; + this.subscriptionHandle = subscriptionHandle; + this.messageType = messageType; + this.topic = topic; + this.callback = callback; + } + + /** + * @return The callback function that this subscription will trigger when + * a message is received. + */ + public final Consumer getCallback() { + return callback; + } + + /** + * @return The type of the messages that this subscription may receive. + */ + public final Class getMessageType() { + return messageType; + } + + /** + * @return The pointer to the underlying ROS2 subscription structure. + */ + public final long getSubscriptionHandle() { + return subscriptionHandle; + } + + public final long getNodeHandle() { + return this.nodeHandle; + } } diff --git a/rcljava/src/main/java/org/ros2/rcljava/package-info.java b/rcljava/src/main/java/org/ros2/rcljava/package-info.java new file mode 100644 index 00000000..8733530e --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/package-info.java @@ -0,0 +1,19 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * An API for using ROS2 with Java. + */ +package org.ros2.rcljava; diff --git a/rcljava/src/test/java/org/ros2/rcljava/NodeTest.java b/rcljava/src/test/java/org/ros2/rcljava/NodeTest.java new file mode 100644 index 00000000..b0184cda --- /dev/null +++ b/rcljava/src/test/java/org/ros2/rcljava/NodeTest.java @@ -0,0 +1,30 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava; + +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class NodeTest { + + @Test + public void testCreate() { + RCLJava.rclJavaInit(); + Node node = RCLJava.createNode("test_node"); + assertNotEquals(0, node.getNodeHandle()); + } +} diff --git a/rcljava/src/test/java/org/ros2/rcljava/PublisherTest.java b/rcljava/src/test/java/org/ros2/rcljava/PublisherTest.java new file mode 100644 index 00000000..ccf4c1dc --- /dev/null +++ b/rcljava/src/test/java/org/ros2/rcljava/PublisherTest.java @@ -0,0 +1,35 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava; + +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class PublisherTest { + + @Test + public void testCreate() { + RCLJava.rclJavaInit(); + Node node = RCLJava.createNode("test_node"); + Publisher publisher = node. + createPublisher(std_msgs.msg.String.class, + "test_topic"); + assertNotEquals(node.getNodeHandle(), publisher.getNodeHandle()); + assertNotEquals(0, publisher.getNodeHandle()); + assertNotEquals(0, publisher.getPublisherHandle()); + } +} diff --git a/rcljava/src/test/java/org/ros2/rcljava/RCLJavaTest.java b/rcljava/src/test/java/org/ros2/rcljava/RCLJavaTest.java new file mode 100644 index 00000000..2a9bacb0 --- /dev/null +++ b/rcljava/src/test/java/org/ros2/rcljava/RCLJavaTest.java @@ -0,0 +1,39 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class RCLJavaTest { + + @Test + public void testInit() { + assertEquals(false, RCLJava.isInitialized()); + RCLJava.rclJavaInit(); + assertEquals(true, RCLJava.isInitialized()); + } + + @Test + public void testOk() { + RCLJava.rclJavaInit(); + assertEquals(true, RCLJava.ok()); + RCLJava.shutdown(); + assertEquals(false, RCLJava.ok()); + } + +} diff --git a/rcljava/src/test/java/org/ros2/rcljava/SubscriptionTest.java b/rcljava/src/test/java/org/ros2/rcljava/SubscriptionTest.java new file mode 100644 index 00000000..d93fa1a3 --- /dev/null +++ b/rcljava/src/test/java/org/ros2/rcljava/SubscriptionTest.java @@ -0,0 +1,39 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava; + +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class SubscriptionTest { + + @Test + public void testCreate() { + RCLJava.rclJavaInit(); + Node node = RCLJava.createNode("test_node"); + Subscription subscription = node. + createSubscription(std_msgs.msg.String.class, + "test_topic", new Consumer() { + public void accept(std_msgs.msg.String msg) { + } + } + ); + assertNotEquals(node.getNodeHandle(), subscription.getNodeHandle()); + assertNotEquals(0, subscription.getNodeHandle()); + assertNotEquals(0, subscription.getSubscriptionHandle()); + } +} diff --git a/rcljava_common/CMakeLists.txt b/rcljava_common/CMakeLists.txt new file mode 100644 index 00000000..43ef3ccb --- /dev/null +++ b/rcljava_common/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(rcljava_common) + +find_package(ament_cmake REQUIRED) + +find_package(ament_cmake_export_jars REQUIRED) +find_package(ament_cmake_export_libraries REQUIRED) + +if(ANDROID) + find_host_package(Java COMPONENTS Development REQUIRED) +else() + find_package(Java COMPONENTS Development REQUIRED) + find_package(JNI REQUIRED) +endif() +include(UseJava) + +if(NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra") +endif() + +set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.6" "-target" "1.6") + +set(${PROJECT_NAME}_java_sources + "src/main/java/org/ros2/rcljava/common/RCLJavaProxy.java" +) + +set(${PROJECT_NAME}_cpp_sources + "src/main/cpp/rcljava_common.cpp" +) + +add_jar("${PROJECT_NAME}_jar" + "${${PROJECT_NAME}_java_sources}" + OUTPUT_NAME + "${PROJECT_NAME}" +) + +install_jar("${PROJECT_NAME}_jar" "share/${PROJECT_NAME}/java") +ament_export_jars("share/${PROJECT_NAME}/java/${PROJECT_NAME}.jar") + +include_directories(include) + +add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_cpp_sources}) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${JNI_INCLUDE_DIRS} +) + +ament_export_include_directories(include) + +ament_export_libraries(${PROJECT_NAME}) + +install( + TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + +install( + DIRECTORY include/ + DESTINATION include +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package( + CONFIG_EXTRAS "rcljava_cmake_module-extras.cmake.in" +) + +install(DIRECTORY cmake + DESTINATION share/${PROJECT_NAME} +) diff --git a/rcljava_common/cmake/Modules/FindJavaExtra.cmake b/rcljava_common/cmake/Modules/FindJavaExtra.cmake new file mode 100644 index 00000000..4bc65e01 --- /dev/null +++ b/rcljava_common/cmake/Modules/FindJavaExtra.cmake @@ -0,0 +1,74 @@ +# Copyright 2016 Esteve Fernandez +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(ANDROID) + find_host_package(Java COMPONENTS Development) +else() + find_package(Java COMPONENTS Development) + find_package(JNI REQUIRED) +endif() + +include(UseJava) + +function(add_junit_tests TARGET_NAME) + + cmake_parse_arguments(_add_junit_tests + "" + "" + "TESTS;SOURCES;INCLUDE_JARS;LIBRARY_PATHS" + ${ARGN} + ) + + set(_source_files ${_add_junit_tests_SOURCES} ${_add_junit_tests_UNPARSED_ARGUMENTS}) + + if(WIN32 AND NOT CYGWIN) + set(SEPARATOR ";") + else() + set(SEPARATOR ":") + endif() + + find_jar(JUNIT_JAR NAMES junit4) + if(NOT ${JUNIT_JAR}) + find_jar(JUNIT_JAR NAMES junit VERSIONS 4) + endif() + + add_jar("${TARGET_NAME}_jar" + "${_source_files}" + OUTPUT_NAME + "${TARGET_NAME}" + INCLUDE_JARS + "${_add_junit_tests_INCLUDE_JARS}" + "${JUNIT_JAR}" + ) + + get_property(_jar_test_file + TARGET "${TARGET_NAME}_jar" + PROPERTY "JAR_FILE" + ) + + set(${TARGET_NAME}_jar_dependencies "${JUNIT_JAR}${SEPARATOR}${_jar_test_file}") + foreach(_jar_dep ${_add_junit_tests_INCLUDE_JARS}) + set(${TARGET_NAME}_jar_dependencies "${${TARGET_NAME}_jar_dependencies}${SEPARATOR}${_jar_dep}") + endforeach() + + string(REPLACE ";" ${SEPARATOR} _library_paths "${_add_junit_tests_LIBRARY_PATHS}") + + add_test(NAME ${TARGET_NAME} + COMMAND ${Java_JAVA_EXECUTABLE} + ${JVMARGS} -classpath ${${TARGET_NAME}_jar_dependencies} -Djava.library.path=${_library_paths} + org.junit.runner.JUnitCore ${_add_junit_tests_TESTS} + ) + +# add_dependencies(${TARGET_NAME} ${TARGET_NAME}_jar) +endfunction() diff --git a/rcljava_common/include/rcljava_common/exceptions.h b/rcljava_common/include/rcljava_common/exceptions.h new file mode 100644 index 00000000..a770e425 --- /dev/null +++ b/rcljava_common/include/rcljava_common/exceptions.h @@ -0,0 +1,22 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef RCLJAVA_COMMON__EXCEPTIONS_H_ +#define RCLJAVA_COMMON__EXCEPTIONS_H_ +#include + +#include + +void rcljava_throw_exception(JNIEnv *, const char *, const std::string &); + +#endif // RCLJAVA_COMMON__EXCEPTIONS_H_ diff --git a/rcljava_common/include/rcljava_common/signatures.h b/rcljava_common/include/rcljava_common/signatures.h new file mode 100644 index 00000000..a4be5381 --- /dev/null +++ b/rcljava_common/include/rcljava_common/signatures.h @@ -0,0 +1,26 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef RCLJAVA_COMMON__SIGNATURES_H_ +#define RCLJAVA_COMMON__SIGNATURES_H_ +#include + +#include + +using convert_from_java_signature = void * (*)(jobject, void *); + +using convert_to_java_signature = jobject (*)(void *, jobject); + +void rcljava_throw_exception(JNIEnv *, const char *, const std::string &); + +#endif // RCLJAVA_COMMON__SIGNATURES_H_ diff --git a/rcljava_common/package.xml b/rcljava_common/package.xml new file mode 100644 index 00000000..56208918 --- /dev/null +++ b/rcljava_common/package.xml @@ -0,0 +1,20 @@ + + + rcljava_common + 0.0.0 + Common code for ROS2 Java projects. + Esteve Fernandez + Apache License 2.0 + + ament_cmake + ament_cmake_export_jars + ament_cmake_export_libraries + rosidl_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/rcljava_common/rcljava_cmake_module-extras.cmake.in b/rcljava_common/rcljava_cmake_module-extras.cmake.in new file mode 100644 index 00000000..367ec6e4 --- /dev/null +++ b/rcljava_common/rcljava_cmake_module-extras.cmake.in @@ -0,0 +1,15 @@ +# Copyright 2016 Esteve Fernandez +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +list(INSERT CMAKE_MODULE_PATH 0 "${rcljava_common_DIR}/Modules") diff --git a/rcljava_common/src/main/cpp/rcljava_common.cpp b/rcljava_common/src/main/cpp/rcljava_common.cpp new file mode 100644 index 00000000..72123203 --- /dev/null +++ b/rcljava_common/src/main/cpp/rcljava_common.cpp @@ -0,0 +1,30 @@ +// Copyright 2016 Esteve Fernandez +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include + +#include +#include + +#include "rcljava_common/exceptions.h" + +void rcljava_throw_exception(JNIEnv * env, const char * class_name, const std::string & message) +{ + jclass exception_class; + + exception_class = env->FindClass(class_name); + + assert(exception_class != nullptr); + + env->ThrowNew(exception_class, message.c_str()); +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/common/RCLJavaProxy.java b/rcljava_common/src/main/java/org/ros2/rcljava/common/RCLJavaProxy.java new file mode 100644 index 00000000..a27a2714 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/common/RCLJavaProxy.java @@ -0,0 +1,62 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava.common; + +import java.lang.reflect.Method; + +/** + * Utility class that allows generated messages to retrieve the typesupport and + * the RMW implementation via reflection. + * This prevents a circular dependency between generated messages and rcljava. + */ +public final class RCLJavaProxy { + /** + * Private constructor so this cannot be instantiated. + */ + private RCLJavaProxy() { } + + /** + * @return a pointer to the underlying typesupport via reflection. + */ + public static synchronized String getTypesupportIdentifier() { + try { + Class cls = Class.forName("org.ros2.rcljava.RCLJava"); + Method meth = cls.getDeclaredMethod( + "getTypesupportIdentifier", (Class[]) null); + Object obj = meth.invoke(null, (Class[]) null); + return (String) obj; + } catch (Exception exc) { + // TODO(esteve): handle exception + return null; + } + } + + /** + * @return a pointer to the underlying typesupport via reflection. + */ + public static synchronized String getRMWIdentifier() { + try { + Class cls = Class.forName("org.ros2.rcljava.RCLJava"); + Method meth = cls.getDeclaredMethod( + "getRMWIdentifier", (Class[]) null); + Object obj = meth.invoke(null, (Class[]) null); + return (String) obj; + } catch (Exception exc) { + // TODO(esteve): handle exception + return null; + } + } +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/common/package-info.java b/rcljava_common/src/main/java/org/ros2/rcljava/common/package-info.java new file mode 100644 index 00000000..8ff9aa83 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/common/package-info.java @@ -0,0 +1,21 @@ +/* Copyright 2016 Esteve Fernandez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Contains a utility class for retrieving information about the RMW and + * typesupport implemnetations via reflection, to avoid a circular + * dependency between rcljava and generated messages. + */ +package org.ros2.rcljava.common; diff --git a/rosidl_generator_java/CMakeLists.txt b/rosidl_generator_java/CMakeLists.txt index 9435e9d2..d9d54636 100644 --- a/rosidl_generator_java/CMakeLists.txt +++ b/rosidl_generator_java/CMakeLists.txt @@ -5,24 +5,26 @@ project(rosidl_generator_java) find_package(ament_cmake REQUIRED) find_package(ament_cmake_export_jars REQUIRED) find_package(ament_cmake_python REQUIRED) -#find_package(rosidl_cmake REQUIRED) +find_package(rosidl_cmake REQUIRED) find_package(rosidl_generator_c REQUIRED) +find_package(rcljava_common REQUIRED) + ament_export_dependencies(rosidl_cmake) ament_python_install_package(${PROJECT_NAME}) -if (ANDROID) +if(ANDROID) find_host_package(Java COMPONENTS Development REQUIRED) else() find_package(Java COMPONENTS Development REQUIRED) find_package(JNI REQUIRED) endif() -include (UseJava) +include(UseJava) set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.6" "-target" "1.6") -if(AMENT_ENABLE_TESTING) +if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() @@ -66,16 +68,6 @@ if(AMENT_ENABLE_TESTING) # TODO(esteve): add support for JUnit tests endif() -set(${PROJECT_NAME}_sources - "src/main/java/org/ros2/rosidl_generator_java/RCLJavaProxy.java" -) - -add_jar("${PROJECT_NAME}_jar" - "${${PROJECT_NAME}_sources}" - OUTPUT_NAME - "${PROJECT_NAME}" -) - install( PROGRAMS bin/rosidl_generator_java DESTINATION lib/rosidl_generator_java @@ -86,9 +78,6 @@ install( DESTINATION share/${PROJECT_NAME} ) -install_jar("${PROJECT_NAME}_jar" "share/${PROJECT_NAME}/java") -ament_export_jars("share/${PROJECT_NAME}/java/${PROJECT_NAME}.jar") - ament_package( CONFIG_EXTRAS "cmake/rosidl_generator_java_get_typesupports.cmake" "cmake/register_java.cmake" diff --git a/rosidl_generator_java/cmake/rosidl_generator_java_generate_interfaces.cmake b/rosidl_generator_java/cmake/rosidl_generator_java_generate_interfaces.cmake index e0e96a6e..fe962392 100644 --- a/rosidl_generator_java/cmake/rosidl_generator_java_generate_interfaces.cmake +++ b/rosidl_generator_java/cmake/rosidl_generator_java_generate_interfaces.cmake @@ -16,14 +16,19 @@ find_package(ament_cmake_export_jars REQUIRED) find_package(rosidl_generator_c REQUIRED) find_package(rmw_implementation_cmake REQUIRED) find_package(rmw REQUIRED) +find_package(rcljava_common REQUIRED) -if (ANDROID) +if(ANDROID) find_host_package(Java COMPONENTS Development REQUIRED) else() find_package(Java COMPONENTS Development REQUIRED) find_package(JNI REQUIRED) endif() -include (UseJava) +include(UseJava) + +if(NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra") +endif() set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.6" "-target" "1.6") @@ -225,24 +230,18 @@ foreach(_generated_msg_cpp_ts_file ${_generated_msg_cpp_ts_files}) ament_target_dependencies(${_package_name}_${_base_msg_name}__${_typesupport_impl} "rosidl_generator_c" + "rosidl_generator_java" + "rcljava_common" "${_typesupport_impl}" + "${PROJECT_NAME}__rosidl_generator_c" ) list(APPEND _extension_dependencies ${_package_name}_${_base_msg_name}__${_typesupport_impl}) - ament_target_dependencies(${_package_name}_${_base_msg_name}__${_typesupport_impl} - ${_typesupport_impl} - ) add_dependencies(${_package_name}_${_base_msg_name}__${_typesupport_impl} ${rosidl_generate_interfaces_TARGET}__${_typesupport_impl} ) - ament_target_dependencies(${_package_name}_${_base_msg_name}__${_typesupport_impl} - "rosidl_generator_c" - "rosidl_generator_java" - "${PROJECT_NAME}__rosidl_generator_c" - ) - if(NOT rosidl_generate_interfaces_SKIP_INSTALL) install(TARGETS ${_package_name}_${_base_msg_name}__${_typesupport_impl} ARCHIVE DESTINATION lib @@ -255,8 +254,8 @@ foreach(_generated_msg_cpp_ts_file ${_generated_msg_cpp_ts_files}) endforeach() set(_jar_deps "") -find_package(rosidl_generator_java REQUIRED) -foreach(_jar_dep ${rosidl_generator_java_JARS}) +find_package(rcljava_common REQUIRED) +foreach(_jar_dep ${rcljava_common_JARS}) list(APPEND _jar_deps "${_jar_dep}") endforeach() diff --git a/rosidl_generator_java/package.xml b/rosidl_generator_java/package.xml index 1d8f801a..677b60df 100644 --- a/rosidl_generator_java/package.xml +++ b/rosidl_generator_java/package.xml @@ -8,10 +8,13 @@ ament_cmake ament_cmake_export_jars + rcljava_common ament_cmake ament_cmake_export_jars rosidl_cmake + rcljava_common + rosidl_generator_c rmw_implementation @@ -19,17 +22,20 @@ rosidl_generator_c rosidl_parser - rosidl_typesupport_connext_c rosidl_typesupport_introspection_c rosidl_typesupport_opensplice_c ---> + ament_cmake diff --git a/rosidl_generator_java/resource/msg.java.template b/rosidl_generator_java/resource/msg.java.template index 57b72dfd..988e13c7 100644 --- a/rosidl_generator_java/resource/msg.java.template +++ b/rosidl_generator_java/resource/msg.java.template @@ -1,6 +1,6 @@ package @(package_name).@(subfolder); -import org.ros2.rosidl_generator_java.RCLJavaProxy; +import org.ros2.rcljava.common.RCLJavaProxy; @[for field in spec.fields]@ @[ if not field.type.is_primitive_type()]@ @@ -10,19 +10,19 @@ import @(field.type.pkg_name).msg.@(field.type.type); @[ end if]@ @[end for]@ -public class @(type_name) { - static { - try { - System.loadLibrary("@(spec.base_type.pkg_name)_@(type_name)_s__" + RCLJavaProxy.getTypesupportIdentifier()); - } catch (UnsatisfiedLinkError e) { - System.err.println("Native code library failed to load.\n" + e); - System.exit(1); - } +public final class @(type_name) { + static { + try { + System.loadLibrary("@(spec.base_type.pkg_name)_@(type_name)_s__" + RCLJavaProxy.getTypesupportIdentifier()); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Native code library failed to load.\n" + ule); + System.exit(1); } + } - public static native long getFromJavaConverter(); - public static native long getToJavaConverter(); - public static native long getTypeSupport(); + public static native long getFromJavaConverter(); + public static native long getToJavaConverter(); + public static native long getTypeSupport(); @[for constant in spec.constants]@ public static final @(get_builtin_java_type(constant.type)) @(constant.name) = @(constant_value_to_java(constant.type, constant.value)); @@ -35,30 +35,30 @@ def upperfirst(s): @[for field in spec.fields]@ @[ if field.type.is_array]@ - private java.util.List<@(get_java_type(field.type, use_primitives=False))> @(field.name); + private java.util.List<@(get_java_type(field.type, use_primitives=False))> @(field.name); - public void set@(upperfirst(field.name))(java.util.Collection<@(get_java_type(field.type, use_primitives=False))> @(field.name)) { - if (@(field.name) != null) { - this.@(field.name) = new java.util.ArrayList<@(get_java_type(field.type, use_primitives=False))>(@(field.name)); - } + public final void set@(upperfirst(field.name))(final java.util.Collection<@(get_java_type(field.type, use_primitives=False))> @(field.name)) { + if (@(field.name) != null) { + this.@(field.name) = new java.util.ArrayList<@(get_java_type(field.type, use_primitives=False))>(@(field.name)); } + } - public java.util.List<@(get_java_type(field.type, use_primitives=False))> get@(upperfirst(field.name))() { - if (this.@(field.name) == null) { - return null; - } - return new java.util.ArrayList<@(get_java_type(field.type, use_primitives=False))>(this.@(field.name)); + public final java.util.List<@(get_java_type(field.type, use_primitives=False))> get@(upperfirst(field.name))() { + if (this.@(field.name) == null) { + return null; } + return new java.util.ArrayList<@(get_java_type(field.type, use_primitives=False))>(this.@(field.name)); + } @[ else]@ - private @(get_java_type(field.type)) @(field.name); + private @(get_java_type(field.type)) @(field.name); - public void set@(upperfirst(field.name))(@(get_java_type(field.type)) @(field.name)) { - this.@(field.name) = @(field.name); - } + public void set@(upperfirst(field.name))(final @(get_java_type(field.type)) @(field.name)) { + this.@(field.name) = @(field.name); + } - public @(get_java_type(field.type)) get@(upperfirst(field.name))() { - return this.@(field.name); - } + public @(get_java_type(field.type)) get@(upperfirst(field.name))() { + return this.@(field.name); + } @[ end if]@ @[end for]@ } diff --git a/rosidl_generator_java/resource/msg_support.entry_point.cpp.template b/rosidl_generator_java/resource/msg_support.entry_point.cpp.template index d3d3ea2a..66bcb123 100644 --- a/rosidl_generator_java/resource/msg_support.entry_point.cpp.template +++ b/rosidl_generator_java/resource/msg_support.entry_point.cpp.template @@ -2,6 +2,7 @@ #include #include #include +#include #include <@(spec.base_type.pkg_name)/@(subfolder)/@(module_name).h> #include @@ -12,6 +13,9 @@ #include #include +#include +#include + @[for field in spec.fields]@ @[ if not field.type.is_primitive_type()]@ #include <@(field.type.pkg_name)/msg/@(convert_camel_case_to_lower_case_underscore(field.type.type)).h> @@ -84,16 +88,16 @@ static JavaVM* g_vm = nullptr; jint _jlist_@(field.name)_size = env->CallIntMethod(_jlist_@(field.name)_object, _jlist_@(field.name)_size_mid); @[ if field.type.type == 'string']@ if (!rosidl_generator_c__String__Array__init(&(ros_message->@(field.name)), _jlist_@(field.name)_size)) { - assert(false); // PyErr_SetString(PyExc_RuntimeError, "unable to create String__Array ros_message"); + rcljava_throw_exception(env, "java/lang/IllegalStateException", "unable to create String__Array ros_message"); } @[ else]@ @[ if field.type.is_primitive_type()]@ if (!rosidl_generator_c__@(field.type.type)__Array__init(&(ros_message->@(field.name)), _jlist_@(field.name)_size)) { - assert(false); // TODO(esteve): error PyErr_SetString(PyExc_RuntimeError, "unable to create @(field.type.type)__Array ros_message"); + rcljava_throw_exception(env, "java/lang/IllegalStateException", "unable to create @(field.type.type)__Array ros_message"); } @[ else]@ if (!@(field.type.pkg_name)__msg__@(field.type.type)__Array__init(&(ros_message->@(field.name)), _jlist_@(field.name)_size)) { - assert(false); // TODO(esteve): error PyErr_SetString(PyExc_RuntimeError, "unable to create @(field.type.type)__Array ros_message"); + rcljava_throw_exception(env, "java/lang/IllegalStateException", "unable to create @(field.type.type)__Array ros_message"); } @[ end if]@ diff --git a/rosidl_generator_java/rosidl_generator_java/__init__.py b/rosidl_generator_java/rosidl_generator_java/__init__.py index 8b114e22..9e824eda 100644 --- a/rosidl_generator_java/rosidl_generator_java/__init__.py +++ b/rosidl_generator_java/rosidl_generator_java/__init__.py @@ -76,7 +76,8 @@ def generate_java(generator_arguments_file, typesupport_impl, typesupport_impls) for generated_filename in generated_filenames: data = { 'constant_value_to_java': constant_value_to_java, - 'convert_camel_case_to_lower_case_underscore': convert_camel_case_to_lower_case_underscore, + 'convert_camel_case_to_lower_case_underscore': + convert_camel_case_to_lower_case_underscore, 'get_builtin_java_type': get_builtin_java_type, 'module_name': module_name, 'package_name': package_name, 'jni_package_name': jni_package_name, diff --git a/rosidl_generator_java/src/main/java/org/ros2/rosidl_generator_java/RCLJavaProxy.java b/rosidl_generator_java/src/main/java/org/ros2/rosidl_generator_java/RCLJavaProxy.java deleted file mode 100644 index 0901482d..00000000 --- a/rosidl_generator_java/src/main/java/org/ros2/rosidl_generator_java/RCLJavaProxy.java +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright 2016 Esteve Fernandez - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ros2.rosidl_generator_java; - -import java.lang.reflect.Method; - -public class RCLJavaProxy { - public static synchronized String getTypesupportIdentifier() { - try { - Class c = Class.forName("org.ros2.rcljava.RCLJava"); - Method m = c.getDeclaredMethod("getTypesupportIdentifier", (Class []) null); - Object o = m.invoke(null, (Class []) null); - return (String)o; - } catch(Exception e) { - // TODO(esteve): handle exception - return null; - } - } - - public static synchronized String getRMWIdentifier() { - try { - Class c = Class.forName("org.ros2.rcljava.RCLJava"); - Method m = c.getDeclaredMethod("getRMWIdentifier", (Class []) null); - Object o = m.invoke(null, (Class []) null); - return (String)o; - } catch(Exception e) { - // TODO(esteve): handle exception - return null; - } - } - -}