Skip to content

Commit d6c5085

Browse files
committed
Add get topic names and type method to Node class (osrf#32)
* Add NameAndTypes class * Add getTopicNamesAndTypes method to Node Signed-off-by: Ivan Santiago Paunovic <ivanpauno@ekumenlabs.com>
1 parent fd2dd7b commit d6c5085

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
@@ -238,3 +240,60 @@ Java_org_ros2_rcljava_node_NodeImpl_nativeCreateTimerHandle(
238240
jlong jtimer = reinterpret_cast<jlong>(timer);
239241
return jtimer;
240242
}
243+
244+
JNIEXPORT void JNICALL
245+
Java_org_ros2_rcljava_node_NodeImpl_nativeGetTopicNamesAndTypes(
246+
JNIEnv * env, jclass, jlong handle, jobject jnames_and_types)
247+
{
248+
rcl_node_t * node = reinterpret_cast<rcl_node_t *>(handle);
249+
if (!node) {
250+
rcljava_throw_exception(env, "java/lang/IllegalArgumentException", "node handle is NULL");
251+
return;
252+
}
253+
254+
jclass collection_clazz = env->FindClass("java/util/Collection");
255+
jmethodID collection_add_mid = env->GetMethodID(
256+
collection_clazz, "add", "(Ljava/lang/Object;)Z");
257+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
258+
jclass name_and_types_clazz = env->FindClass("org/ros2/rcljava/graph/NameAndTypes");
259+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
260+
jmethodID name_and_types_init_mid = env->GetMethodID(name_and_types_clazz, "<init>", "()V");
261+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
262+
jfieldID name_fid = env->GetFieldID(name_and_types_clazz, "name", "Ljava/lang/String;");
263+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
264+
jfieldID types_fid = env->GetFieldID(name_and_types_clazz, "types", "Ljava/util/Collection;");
265+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env);
266+
267+
rcl_allocator_t allocator = rcl_get_default_allocator();
268+
rcl_names_and_types_t topic_names_and_types = rcl_get_zero_initialized_names_and_types();
269+
270+
rcl_ret_t ret = rcl_get_topic_names_and_types(
271+
node,
272+
&allocator,
273+
false,
274+
&topic_names_and_types);
275+
RCLJAVA_COMMON_THROW_FROM_RCL(env, ret, "failed to get topic names and types");
276+
277+
for (size_t i = 0; i < topic_names_and_types.names.size; i++) {
278+
jobject jitem = env->NewObject(name_and_types_clazz, name_and_types_init_mid);
279+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
280+
jstring jname = env->NewStringUTF(topic_names_and_types.names.data[i]);
281+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
282+
env->SetObjectField(jitem, name_fid, jname);
283+
// the default constructor already inits types to an empty ArrayList
284+
jobject jtypes = env->GetObjectField(jitem, types_fid);
285+
for (size_t j = 0; j < topic_names_and_types.types[i].size; j++) {
286+
jstring jtype = env->NewStringUTF(topic_names_and_types.types[i].data[j]);
287+
env->CallBooleanMethod(jtypes, collection_add_mid, jtype);
288+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
289+
}
290+
env->CallBooleanMethod(jnames_and_types, collection_add_mid, jitem);
291+
RCLJAVA_COMMON_CHECK_FOR_EXCEPTION_WITH_ERROR_STATEMENT(env, goto cleanup);
292+
}
293+
294+
cleanup:
295+
ret = rcl_names_and_types_fini(&topic_names_and_types);
296+
if (!env->ExceptionCheck() && RCL_RET_OK != ret) {
297+
rcljava_throw_rclexception(env, ret, "failed to fini topic names and types structure");
298+
}
299+
}
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.Disposable;
2829
import org.ros2.rcljava.interfaces.MessageDefinition;
@@ -753,4 +754,13 @@ public rcl_interfaces.msg.ListParametersResult listParameters(
753754
return result;
754755
}
755756
}
757+
758+
public final Collection<NameAndTypes> getTopicNamesAndTypes() {
759+
Collection<NameAndTypes> namesAndTypes = new ArrayList();
760+
nativeGetTopicNamesAndTypes(this.handle, namesAndTypes);
761+
return namesAndTypes;
762+
}
763+
764+
private static native final void nativeGetTopicNamesAndTypes(
765+
long handle, Collection<NameAndTypes> namesAndTypes);
756766
}

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;
@@ -28,7 +29,10 @@
2829
import java.lang.ref.WeakReference;
2930
import java.lang.reflect.Method;
3031

32+
import java.util.concurrent.TimeUnit;
33+
import java.util.ArrayList;
3134
import java.util.Arrays;
35+
import java.util.Collection;
3236
import java.util.List;
3337

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

0 commit comments

Comments
 (0)