Skip to content

Commit 2741971

Browse files
authored
Add get topic names and type method to Node class (#32)
* Add NameAndTypes class * Add getTopicNamesAndTypes method to Node Signed-off-by: Ivan Santiago Paunovic <[email protected]>
1 parent a6b5a0b commit 2741971

File tree

7 files changed

+237
-0
lines changed

7 files changed

+237
-0
lines changed

rcljava/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ set(${PROJECT_NAME}_sources
141141
"src/main/java/org/ros2/rcljava/events/EventStatus.java"
142142
"src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java"
143143
"src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java"
144+
"src/main/java/org/ros2/rcljava/graph/NameAndTypes.java"
144145
"src/main/java/org/ros2/rcljava/publisher/statuses/LivelinessLost.java"
145146
"src/main/java/org/ros2/rcljava/publisher/statuses/OfferedDeadlineMissed.java"
146147
"src/main/java/org/ros2/rcljava/publisher/statuses/OfferedQosIncompatible.java"

rcljava/include/org_ros2_rcljava_node_NodeImpl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ JNIEXPORT jlong
9292
JNICALL Java_org_ros2_rcljava_node_NodeImpl_nativeCreateTimerHandle(
9393
JNIEnv *, jclass, jlong, jlong, jlong);
9494

95+
/*
96+
* Class: org_ros2_rcljava_node_NodeImpl
97+
* Method: nativeGetTopicNamesAndTypes
98+
* Signature: (JLjava/util/Collection;)V
99+
*/
100+
JNIEXPORT void
101+
JNICALL Java_org_ros2_rcljava_node_NodeImpl_nativeGetTopicNamesAndTypes(
102+
JNIEnv *, jclass, jlong, jobject);
103+
95104
#ifdef __cplusplus
96105
}
97106
#endif

rcljava/src/main/cpp/org_ros2_rcljava_node_NodeImpl.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020

2121
#include "rcl/error_handling.h"
22+
#include "rcl/graph.h"
2223
#include "rcl/node.h"
2324
#include "rcl/rcl.h"
2425
#include "rmw/rmw.h"
@@ -29,6 +30,7 @@
2930

3031
#include "org_ros2_rcljava_node_NodeImpl.h"
3132

33+
using rcljava_common::exceptions::rcljava_throw_exception;
3234
using rcljava_common::exceptions::rcljava_throw_rclexception;
3335

3436
JNIEXPORT jstring JNICALL
@@ -250,3 +252,60 @@ Java_org_ros2_rcljava_node_NodeImpl_nativeCreateTimerHandle(
250252
jlong jtimer = reinterpret_cast<jlong>(timer);
251253
return jtimer;
252254
}
255+
256+
JNIEXPORT void JNICALL
257+
Java_org_ros2_rcljava_node_NodeImpl_nativeGetTopicNamesAndTypes(
258+
JNIEnv * env, jclass, jlong handle, jobject jnames_and_types)
259+
{
260+
rcl_node_t * node = reinterpret_cast<rcl_node_t *>(handle);
261+
if (!node) {
262+
rcljava_throw_exception(env, "java/lang/IllegalArgumentException", "node handle is NULL");
263+
return;
264+
}
265+
266+
jclass collection_clazz = env->FindClass("java/util/Collection");
267+
jmethodID collection_add_mid = env->GetMethodID(
268+
collection_clazz, "add", "(Ljava/lang/Object;)Z");
269+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
270+
jclass name_and_types_clazz = env->FindClass("org/ros2/rcljava/graph/NameAndTypes");
271+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
272+
jmethodID name_and_types_init_mid = env->GetMethodID(name_and_types_clazz, "<init>", "()V");
273+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
274+
jfieldID name_fid = env->GetFieldID(name_and_types_clazz, "name", "Ljava/lang/String;");
275+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
276+
jfieldID types_fid = env->GetFieldID(name_and_types_clazz, "types", "Ljava/util/Collection;");
277+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
278+
279+
rcl_allocator_t allocator = rcl_get_default_allocator();
280+
rcl_names_and_types_t topic_names_and_types = rcl_get_zero_initialized_names_and_types();
281+
282+
rcl_ret_t ret = rcl_get_topic_names_and_types(
283+
node,
284+
&allocator,
285+
false,
286+
&topic_names_and_types);
287+
RCLJAVA_COMMON_THROW_FROM_RCL(env, ret, "failed to get topic names and types");
288+
289+
for (size_t i = 0; i < topic_names_and_types.names.size; i++) {
290+
jobject jitem = env->NewObject(name_and_types_clazz, name_and_types_init_mid);
291+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
292+
jstring jname = env->NewStringUTF(topic_names_and_types.names.data[i]);
293+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
294+
env->SetObjectField(jitem, name_fid, jname);
295+
// the default constructor already inits types to an empty ArrayList
296+
jobject jtypes = env->GetObjectField(jitem, types_fid);
297+
for (size_t j = 0; j < topic_names_and_types.types[i].size; j++) {
298+
jstring jtype = env->NewStringUTF(topic_names_and_types.types[i].data[j]);
299+
env->CallBooleanMethod(jtypes, collection_add_mid, jtype);
300+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
301+
}
302+
env->CallBooleanMethod(jnames_and_types, collection_add_mid, jitem);
303+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
304+
}
305+
306+
cleanup:
307+
ret = rcl_names_and_types_fini(&topic_names_and_types);
308+
if (!env->ExceptionCheck() && RCL_RET_OK != ret) {
309+
rcljava_throw_rclexception(env, ret, "failed to fini topic names and types structure");
310+
}
311+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/* Copyright 2020 Open Source Robotics Foundation, Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package org.ros2.rcljava.graph;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collection;
20+
import java.util.Objects;
21+
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
25+
import org.ros2.rcljava.common.JNIUtils;
26+
27+
/**
28+
* Class that represents a topic/service/action name, together with all the types
29+
* of message/service/action that are using that name.
30+
*/
31+
public class NameAndTypes {
32+
/// Name of the topic/service/action.
33+
public String name;
34+
/// Types of the topic/service/action with the given name.
35+
public Collection<String> types;
36+
37+
/**
38+
* Construct from given name and types.
39+
*
40+
* @param name name of the topic/service/action.
41+
* @param types types of the given topic/service/action.
42+
* A shallow copy of the given collection will be stored,
43+
* but given that String is immutable, this is not a problem.
44+
* @param typesSize size of the \a typesHandle array.
45+
*/
46+
public NameAndTypes(final String name, final Collection<String> types) {
47+
this.name = name;
48+
this.types = new ArrayList(types);
49+
}
50+
51+
/// @internal Default constructor, only used from jni code.
52+
private NameAndTypes() {
53+
this.types = new ArrayList();
54+
}
55+
56+
@Override
57+
public boolean equals(final Object o) {
58+
if (o == this) {
59+
return true;
60+
}
61+
if (!(o instanceof NameAndTypes)) {
62+
return false;
63+
}
64+
NameAndTypes other = (NameAndTypes) o;
65+
return Objects.equals(this.name, other.name) &&
66+
Objects.equals(this.types, other.types);
67+
}
68+
69+
@Override
70+
public int hashCode() {
71+
return Objects.hash(this.name, this.types);
72+
}
73+
}

rcljava/src/main/java/org/ros2/rcljava/node/Node.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.ros2.rcljava.concurrent.Callback;
2525
import org.ros2.rcljava.consumers.Consumer;
2626
import org.ros2.rcljava.consumers.TriConsumer;
27+
import org.ros2.rcljava.graph.NameAndTypes;
2728
import org.ros2.rcljava.qos.QoSProfile;
2829
import org.ros2.rcljava.interfaces.Disposable;
2930
import org.ros2.rcljava.interfaces.MessageDefinition;
@@ -549,4 +550,12 @@ <T extends ServiceDefinition> Client<T> createClient(final Class<T> serviceType,
549550
* @return rcl_interfaces.msg.ListParametersResult
550551
*/
551552
rcl_interfaces.msg.ListParametersResult listParameters(List<String> prefixes, long depth);
553+
554+
/**
555+
* Return the topics names and types that were detected in the graph.
556+
* See @{link graph#NameAndTypes} for more information about the returned value.
557+
*
558+
* @return the detected topic names and types.
559+
*/
560+
Collection<NameAndTypes> getTopicNamesAndTypes();
552561
}

rcljava/src/main/java/org/ros2/rcljava/node/NodeImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.ros2.rcljava.consumers.Consumer;
2424
import org.ros2.rcljava.consumers.TriConsumer;
2525
import org.ros2.rcljava.contexts.Context;
26+
import org.ros2.rcljava.graph.NameAndTypes;
2627
import org.ros2.rcljava.qos.QoSProfile;
2728
import org.ros2.rcljava.interfaces.MessageDefinition;
2829
import org.ros2.rcljava.interfaces.ServiceDefinition;
@@ -738,4 +739,13 @@ public rcl_interfaces.msg.ListParametersResult listParameters(
738739
return result;
739740
}
740741
}
742+
743+
public final Collection<NameAndTypes> getTopicNamesAndTypes() {
744+
Collection<NameAndTypes> namesAndTypes = new ArrayList();
745+
nativeGetTopicNamesAndTypes(this.handle, namesAndTypes);
746+
return namesAndTypes;
747+
}
748+
749+
private static native final void nativeGetTopicNamesAndTypes(
750+
long handle, Collection<NameAndTypes> namesAndTypes);
741751
}

rcljava/src/test/java/org/ros2/rcljava/node/NodeTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.junit.Assert.assertEquals;
1919
import static org.junit.Assert.assertNotEquals;
20+
import static org.junit.Assert.assertNotNull;
2021
import static org.junit.Assert.assertTrue;
2122

2223
import org.junit.After;
@@ -27,7 +28,10 @@
2728

2829
import java.lang.ref.WeakReference;
2930

31+
import java.util.concurrent.TimeUnit;
32+
import java.util.ArrayList;
3033
import java.util.Arrays;
34+
import java.util.Collection;
3135
import java.util.List;
3236

3337
import org.ros2.rcljava.RCLJava;
@@ -36,6 +40,7 @@
3640
import org.ros2.rcljava.executors.Executor;
3741
import org.ros2.rcljava.executors.MultiThreadedExecutor;
3842
import org.ros2.rcljava.executors.SingleThreadedExecutor;
43+
import org.ros2.rcljava.graph.NameAndTypes;
3944
import org.ros2.rcljava.node.Node;
4045
import org.ros2.rcljava.publisher.Publisher;
4146
import org.ros2.rcljava.subscription.Subscription;
@@ -857,4 +862,75 @@ public Node getNode() {
857862
subscriptionTwo.dispose();
858863
assertEquals(0, subscriptionTwo.getHandle());
859864
}
865+
866+
@Test
867+
public final void testGetTopicNamesAndTypes() throws Exception {
868+
Publisher<rcljava.msg.UInt32> publisher = node.<rcljava.msg.UInt32>createPublisher(
869+
rcljava.msg.UInt32.class, "test_get_topic_names_and_types_one");
870+
Publisher<rcljava.msg.UInt32> publisher2 = node.<rcljava.msg.UInt32>createPublisher(
871+
rcljava.msg.UInt32.class, "test_get_topic_names_and_types_two");
872+
Subscription<rcljava.msg.Empty> subscription = node.<rcljava.msg.Empty>createSubscription(
873+
rcljava.msg.Empty.class, "test_get_topic_names_and_types_one",
874+
new Consumer<rcljava.msg.Empty>() {
875+
public void accept(final rcljava.msg.Empty msg) {}
876+
});
877+
Subscription<rcljava.msg.Empty> subscription2 = node.<rcljava.msg.Empty>createSubscription(
878+
rcljava.msg.Empty.class, "test_get_topic_names_and_types_three",
879+
new Consumer<rcljava.msg.Empty>() {
880+
public void accept(final rcljava.msg.Empty msg) {}
881+
});
882+
883+
Consumer<Collection<NameAndTypes>> validateNameAndTypes =
884+
new Consumer<Collection<NameAndTypes>>() {
885+
public void accept(final Collection<NameAndTypes> namesAndTypes) {
886+
// TODO(ivanpauno): Using assertj may help a lot here https://assertj.github.io/doc/.
887+
assertEquals(namesAndTypes.size(), 3);
888+
assertTrue(
889+
"topic 'test_get_topic_names_and_types_one' was not discovered",
890+
namesAndTypes.contains(
891+
new NameAndTypes(
892+
"/test_get_topic_names_and_types_one",
893+
new ArrayList(Arrays.asList("rcljava/msg/Empty", "rcljava/msg/UInt32")))));
894+
assertTrue(
895+
"topic 'test_get_topic_names_and_types_two' was not discovered",
896+
namesAndTypes.contains(
897+
new NameAndTypes(
898+
"/test_get_topic_names_and_types_two",
899+
new ArrayList(Arrays.asList("rcljava/msg/UInt32")))));
900+
assertTrue(
901+
"topic 'test_get_topic_names_and_types_three' was not discovered",
902+
namesAndTypes.contains(
903+
new NameAndTypes(
904+
"/test_get_topic_names_and_types_three",
905+
new ArrayList(Arrays.asList("rcljava/msg/Empty")))));
906+
}
907+
};
908+
909+
long start = System.currentTimeMillis();
910+
boolean ok = false;
911+
Collection<NameAndTypes> namesAndTypes = null;
912+
do {
913+
namesAndTypes = this.node.getTopicNamesAndTypes();
914+
try {
915+
validateNameAndTypes.accept(namesAndTypes);
916+
ok = true;
917+
} catch (AssertionError err) {
918+
// ignore here, it's going to be validated again at the end.
919+
}
920+
// TODO(ivanpauno): We could wait for the graph guard condition to be triggered if that
921+
// would be available.
922+
try {
923+
TimeUnit.MILLISECONDS.sleep(100);
924+
} catch (InterruptedException err) {
925+
// ignore
926+
}
927+
} while (!ok && System.currentTimeMillis() < start + 1000);
928+
assertNotNull(namesAndTypes);
929+
validateNameAndTypes.accept(namesAndTypes);
930+
931+
publisher.dispose();
932+
publisher2.dispose();
933+
subscription.dispose();
934+
subscription2.dispose();
935+
}
860936
}

0 commit comments

Comments
 (0)