001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005// THIS FILE WAS AUTO-GENERATED BY ./ntcore/generate_topics.py. DO NOT MODIFY 006 007package edu.wpi.first.networktables; 008 009import edu.wpi.first.datalog.DataLog; 010import edu.wpi.first.util.WPIUtilJNI; 011import edu.wpi.first.util.concurrent.Event; 012import edu.wpi.first.util.protobuf.Protobuf; 013import edu.wpi.first.util.struct.Struct; 014import java.nio.charset.StandardCharsets; 015import java.util.EnumSet; 016import java.util.HashMap; 017import java.util.HashSet; 018import java.util.Map; 019import java.util.OptionalLong; 020import java.util.Set; 021import java.util.concurrent.ConcurrentHashMap; 022import java.util.concurrent.ConcurrentMap; 023import java.util.concurrent.TimeUnit; 024import java.util.concurrent.locks.Condition; 025import java.util.concurrent.locks.ReentrantLock; 026import java.util.function.Consumer; 027import us.hebi.quickbuf.ProtoMessage; 028 029/** 030 * NetworkTables Instance. 031 * 032 * <p>Instances are completely independent from each other. Table operations on one instance will 033 * not be visible to other instances unless the instances are connected via the network. The main 034 * limitation on instances is that you cannot have two servers on the same network port. The main 035 * utility of instances is for unit testing, but they can also enable one program to connect to two 036 * different NetworkTables networks. 037 * 038 * <p>The global "default" instance (as returned by {@link #getDefault()}) is always available, and 039 * is intended for the common case when there is only a single NetworkTables instance being used in 040 * the program. 041 * 042 * <p>Additional instances can be created with the {@link #create()} function. A reference must be 043 * kept to the NetworkTableInstance returned by this function to keep it from being garbage 044 * collected. 045 */ 046public final class NetworkTableInstance implements AutoCloseable { 047 /** Client/server mode flag values (as returned by {@link #getNetworkMode()}). */ 048 public enum NetworkMode { 049 /** Running in server mode. */ 050 kServer(0x01), 051 052 /** Running in client mode. */ 053 kClient(0x04), 054 055 /** Currently starting up (either client or server). */ 056 kStarting(0x08), 057 058 /** Running in local-only mode. */ 059 kLocal(0x10); 060 061 private final int value; 062 063 NetworkMode(int value) { 064 this.value = value; 065 } 066 067 /** 068 * Returns the network mode value. 069 * 070 * @return The network mode value. 071 */ 072 public int getValue() { 073 return value; 074 } 075 } 076 077 /** The default port that network tables operates on. */ 078 public static final int kDefaultPort = 5810; 079 080 /** 081 * Construct from native handle. 082 * 083 * @param handle Native handle 084 */ 085 private NetworkTableInstance(int handle) { 086 m_owned = false; 087 m_handle = handle; 088 } 089 090 /** Destroys the instance (if created by {@link #create()}). */ 091 @Override 092 public synchronized void close() { 093 if (m_owned && m_handle != 0) { 094 m_listeners.close(); 095 m_schemas.forEach((k, v) -> v.close()); 096 NetworkTablesJNI.destroyInstance(m_handle); 097 m_handle = 0; 098 } 099 } 100 101 /** 102 * Determines if the native handle is valid. 103 * 104 * @return True if the native handle is valid, false otherwise. 105 */ 106 public boolean isValid() { 107 return m_handle != 0; 108 } 109 110 /* The default instance. */ 111 private static NetworkTableInstance s_defaultInstance; 112 113 /** 114 * Get global default instance. 115 * 116 * @return Global default instance 117 */ 118 public static synchronized NetworkTableInstance getDefault() { 119 if (s_defaultInstance == null) { 120 s_defaultInstance = new NetworkTableInstance(NetworkTablesJNI.getDefaultInstance()); 121 } 122 return s_defaultInstance; 123 } 124 125 /** 126 * Create an instance. Note: A reference to the returned instance must be retained to ensure the 127 * instance is not garbage collected. 128 * 129 * @return Newly created instance 130 */ 131 public static NetworkTableInstance create() { 132 NetworkTableInstance inst = new NetworkTableInstance(NetworkTablesJNI.createInstance()); 133 inst.m_owned = true; 134 return inst; 135 } 136 137 /** 138 * Create an instance from a native handle. This instance is not owned, closing will do nothing. 139 * 140 * @param handle Native instance handle 141 * @return Instance 142 */ 143 public static NetworkTableInstance fromNativeHandle(int handle) { 144 return new NetworkTableInstance(handle); 145 } 146 147 /** 148 * Gets the native handle for the instance. 149 * 150 * @return Native handle 151 */ 152 public int getHandle() { 153 return m_handle; 154 } 155 156 /** 157 * Get (generic) topic. 158 * 159 * @param name topic name 160 * @return Topic 161 */ 162 public Topic getTopic(String name) { 163 Topic topic = m_topics.get(name); 164 if (topic == null) { 165 int handle = NetworkTablesJNI.getTopic(m_handle, name); 166 topic = new Topic(this, handle); 167 Topic oldTopic = m_topics.putIfAbsent(name, topic); 168 if (oldTopic != null) { 169 topic = oldTopic; 170 } 171 // also cache by handle 172 m_topicsByHandle.putIfAbsent(handle, topic); 173 } 174 return topic; 175 } 176 177 /** 178 * Get boolean topic. 179 * 180 * @param name topic name 181 * @return BooleanTopic 182 */ 183 public BooleanTopic getBooleanTopic(String name) { 184 Topic topic = m_topics.get(name); 185 if (topic instanceof BooleanTopic t) { 186 return t; 187 } 188 189 int handle; 190 if (topic == null) { 191 handle = NetworkTablesJNI.getTopic(m_handle, name); 192 } else { 193 handle = topic.getHandle(); 194 } 195 196 BooleanTopic wrapTopic = new BooleanTopic(this, handle); 197 m_topics.put(name, wrapTopic); 198 199 // also cache by handle 200 m_topicsByHandle.put(handle, wrapTopic); 201 202 return wrapTopic; 203 } 204 205 /** 206 * Get long topic. 207 * 208 * @param name topic name 209 * @return IntegerTopic 210 */ 211 public IntegerTopic getIntegerTopic(String name) { 212 Topic topic = m_topics.get(name); 213 if (topic instanceof IntegerTopic t) { 214 return t; 215 } 216 217 int handle; 218 if (topic == null) { 219 handle = NetworkTablesJNI.getTopic(m_handle, name); 220 } else { 221 handle = topic.getHandle(); 222 } 223 224 IntegerTopic wrapTopic = new IntegerTopic(this, handle); 225 m_topics.put(name, wrapTopic); 226 227 // also cache by handle 228 m_topicsByHandle.put(handle, wrapTopic); 229 230 return wrapTopic; 231 } 232 233 /** 234 * Get float topic. 235 * 236 * @param name topic name 237 * @return FloatTopic 238 */ 239 public FloatTopic getFloatTopic(String name) { 240 Topic topic = m_topics.get(name); 241 if (topic instanceof FloatTopic t) { 242 return t; 243 } 244 245 int handle; 246 if (topic == null) { 247 handle = NetworkTablesJNI.getTopic(m_handle, name); 248 } else { 249 handle = topic.getHandle(); 250 } 251 252 FloatTopic wrapTopic = new FloatTopic(this, handle); 253 m_topics.put(name, wrapTopic); 254 255 // also cache by handle 256 m_topicsByHandle.put(handle, wrapTopic); 257 258 return wrapTopic; 259 } 260 261 /** 262 * Get double topic. 263 * 264 * @param name topic name 265 * @return DoubleTopic 266 */ 267 public DoubleTopic getDoubleTopic(String name) { 268 Topic topic = m_topics.get(name); 269 if (topic instanceof DoubleTopic t) { 270 return t; 271 } 272 273 int handle; 274 if (topic == null) { 275 handle = NetworkTablesJNI.getTopic(m_handle, name); 276 } else { 277 handle = topic.getHandle(); 278 } 279 280 DoubleTopic wrapTopic = new DoubleTopic(this, handle); 281 m_topics.put(name, wrapTopic); 282 283 // also cache by handle 284 m_topicsByHandle.put(handle, wrapTopic); 285 286 return wrapTopic; 287 } 288 289 /** 290 * Get String topic. 291 * 292 * @param name topic name 293 * @return StringTopic 294 */ 295 public StringTopic getStringTopic(String name) { 296 Topic topic = m_topics.get(name); 297 if (topic instanceof StringTopic t) { 298 return t; 299 } 300 301 int handle; 302 if (topic == null) { 303 handle = NetworkTablesJNI.getTopic(m_handle, name); 304 } else { 305 handle = topic.getHandle(); 306 } 307 308 StringTopic wrapTopic = new StringTopic(this, handle); 309 m_topics.put(name, wrapTopic); 310 311 // also cache by handle 312 m_topicsByHandle.put(handle, wrapTopic); 313 314 return wrapTopic; 315 } 316 317 /** 318 * Get byte[] topic. 319 * 320 * @param name topic name 321 * @return RawTopic 322 */ 323 public RawTopic getRawTopic(String name) { 324 Topic topic = m_topics.get(name); 325 if (topic instanceof RawTopic t) { 326 return t; 327 } 328 329 int handle; 330 if (topic == null) { 331 handle = NetworkTablesJNI.getTopic(m_handle, name); 332 } else { 333 handle = topic.getHandle(); 334 } 335 336 RawTopic wrapTopic = new RawTopic(this, handle); 337 m_topics.put(name, wrapTopic); 338 339 // also cache by handle 340 m_topicsByHandle.put(handle, wrapTopic); 341 342 return wrapTopic; 343 } 344 345 /** 346 * Get boolean[] topic. 347 * 348 * @param name topic name 349 * @return BooleanArrayTopic 350 */ 351 public BooleanArrayTopic getBooleanArrayTopic(String name) { 352 Topic topic = m_topics.get(name); 353 if (topic instanceof BooleanArrayTopic t) { 354 return t; 355 } 356 357 int handle; 358 if (topic == null) { 359 handle = NetworkTablesJNI.getTopic(m_handle, name); 360 } else { 361 handle = topic.getHandle(); 362 } 363 364 BooleanArrayTopic wrapTopic = new BooleanArrayTopic(this, handle); 365 m_topics.put(name, wrapTopic); 366 367 // also cache by handle 368 m_topicsByHandle.put(handle, wrapTopic); 369 370 return wrapTopic; 371 } 372 373 /** 374 * Get long[] topic. 375 * 376 * @param name topic name 377 * @return IntegerArrayTopic 378 */ 379 public IntegerArrayTopic getIntegerArrayTopic(String name) { 380 Topic topic = m_topics.get(name); 381 if (topic instanceof IntegerArrayTopic t) { 382 return t; 383 } 384 385 int handle; 386 if (topic == null) { 387 handle = NetworkTablesJNI.getTopic(m_handle, name); 388 } else { 389 handle = topic.getHandle(); 390 } 391 392 IntegerArrayTopic wrapTopic = new IntegerArrayTopic(this, handle); 393 m_topics.put(name, wrapTopic); 394 395 // also cache by handle 396 m_topicsByHandle.put(handle, wrapTopic); 397 398 return wrapTopic; 399 } 400 401 /** 402 * Get float[] topic. 403 * 404 * @param name topic name 405 * @return FloatArrayTopic 406 */ 407 public FloatArrayTopic getFloatArrayTopic(String name) { 408 Topic topic = m_topics.get(name); 409 if (topic instanceof FloatArrayTopic t) { 410 return t; 411 } 412 413 int handle; 414 if (topic == null) { 415 handle = NetworkTablesJNI.getTopic(m_handle, name); 416 } else { 417 handle = topic.getHandle(); 418 } 419 420 FloatArrayTopic wrapTopic = new FloatArrayTopic(this, handle); 421 m_topics.put(name, wrapTopic); 422 423 // also cache by handle 424 m_topicsByHandle.put(handle, wrapTopic); 425 426 return wrapTopic; 427 } 428 429 /** 430 * Get double[] topic. 431 * 432 * @param name topic name 433 * @return DoubleArrayTopic 434 */ 435 public DoubleArrayTopic getDoubleArrayTopic(String name) { 436 Topic topic = m_topics.get(name); 437 if (topic instanceof DoubleArrayTopic t) { 438 return t; 439 } 440 441 int handle; 442 if (topic == null) { 443 handle = NetworkTablesJNI.getTopic(m_handle, name); 444 } else { 445 handle = topic.getHandle(); 446 } 447 448 DoubleArrayTopic wrapTopic = new DoubleArrayTopic(this, handle); 449 m_topics.put(name, wrapTopic); 450 451 // also cache by handle 452 m_topicsByHandle.put(handle, wrapTopic); 453 454 return wrapTopic; 455 } 456 457 /** 458 * Get String[] topic. 459 * 460 * @param name topic name 461 * @return StringArrayTopic 462 */ 463 public StringArrayTopic getStringArrayTopic(String name) { 464 Topic topic = m_topics.get(name); 465 if (topic instanceof StringArrayTopic t) { 466 return t; 467 } 468 469 int handle; 470 if (topic == null) { 471 handle = NetworkTablesJNI.getTopic(m_handle, name); 472 } else { 473 handle = topic.getHandle(); 474 } 475 476 StringArrayTopic wrapTopic = new StringArrayTopic(this, handle); 477 m_topics.put(name, wrapTopic); 478 479 // also cache by handle 480 m_topicsByHandle.put(handle, wrapTopic); 481 482 return wrapTopic; 483 } 484 485 486 /** 487 * Get protobuf-encoded value topic. 488 * 489 * @param <T> value class (inferred from proto) 490 * @param <MessageType> protobuf message type (inferred from proto) 491 * @param name topic name 492 * @param proto protobuf serialization implementation 493 * @return ProtobufTopic 494 */ 495 public <T, MessageType extends ProtoMessage<?>> 496 ProtobufTopic<T> getProtobufTopic(String name, Protobuf<T, MessageType> proto) { 497 Topic topic = m_topics.get(name); 498 if (topic instanceof ProtobufTopic<?> t && t.getProto().equals(proto)) { 499 @SuppressWarnings("unchecked") 500 ProtobufTopic<T> wrapTopic = (ProtobufTopic<T>) topic; 501 return wrapTopic; 502 } 503 504 int handle; 505 if (topic == null) { 506 handle = NetworkTablesJNI.getTopic(m_handle, name); 507 } else { 508 handle = topic.getHandle(); 509 } 510 511 ProtobufTopic<T> wrapTopic = ProtobufTopic.wrap(this, handle, proto); 512 m_topics.put(name, wrapTopic); 513 514 // also cache by handle 515 m_topicsByHandle.put(handle, wrapTopic); 516 517 return wrapTopic; 518 } 519 520 /** 521 * Get struct-encoded value topic. 522 * 523 * @param <T> value class (inferred from struct) 524 * @param name topic name 525 * @param struct struct serialization implementation 526 * @return StructTopic 527 */ 528 public <T> 529 StructTopic<T> getStructTopic(String name, Struct<T> struct) { 530 Topic topic = m_topics.get(name); 531 if (topic instanceof StructTopic<?> t && t.getStruct().equals(struct)) { 532 @SuppressWarnings("unchecked") 533 StructTopic<T> wrapTopic = (StructTopic<T>) topic; 534 return wrapTopic; 535 } 536 537 int handle; 538 if (topic == null) { 539 handle = NetworkTablesJNI.getTopic(m_handle, name); 540 } else { 541 handle = topic.getHandle(); 542 } 543 544 StructTopic<T> wrapTopic = StructTopic.wrap(this, handle, struct); 545 m_topics.put(name, wrapTopic); 546 547 // also cache by handle 548 m_topicsByHandle.put(handle, wrapTopic); 549 550 return wrapTopic; 551 } 552 553 /** 554 * Get struct-encoded value array topic. 555 * 556 * @param <T> value class (inferred from struct) 557 * @param name topic name 558 * @param struct struct serialization implementation 559 * @return StructArrayTopic 560 */ 561 public <T> 562 StructArrayTopic<T> getStructArrayTopic(String name, Struct<T> struct) { 563 Topic topic = m_topics.get(name); 564 if (topic instanceof StructArrayTopic<?> t && t.getStruct().equals(struct)) { 565 @SuppressWarnings("unchecked") 566 StructArrayTopic<T> wrapTopic = (StructArrayTopic<T>) topic; 567 return wrapTopic; 568 } 569 570 int handle; 571 if (topic == null) { 572 handle = NetworkTablesJNI.getTopic(m_handle, name); 573 } else { 574 handle = topic.getHandle(); 575 } 576 577 StructArrayTopic<T> wrapTopic = StructArrayTopic.wrap(this, handle, struct); 578 m_topics.put(name, wrapTopic); 579 580 // also cache by handle 581 m_topicsByHandle.put(handle, wrapTopic); 582 583 return wrapTopic; 584 } 585 586 private Topic[] topicHandlesToTopics(int[] handles) { 587 Topic[] topics = new Topic[handles.length]; 588 for (int i = 0; i < handles.length; i++) { 589 topics[i] = getCachedTopic(handles[i]); 590 } 591 return topics; 592 } 593 594 /** 595 * Get all published topics. 596 * 597 * @return Array of topics. 598 */ 599 public Topic[] getTopics() { 600 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, "", 0)); 601 } 602 603 /** 604 * Get published topics starting with the given prefix. The results are optionally filtered by 605 * string prefix to only return a subset of all topics. 606 * 607 * @param prefix topic name required prefix; only topics whose name starts with this string are 608 * returned 609 * @return Array of topic information. 610 */ 611 public Topic[] getTopics(String prefix) { 612 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, prefix, 0)); 613 } 614 615 /** 616 * Get published topics starting with the given prefix. The results are optionally filtered by 617 * string prefix and data type to only return a subset of all topics. 618 * 619 * @param prefix topic name required prefix; only topics whose name starts with this string are 620 * returned 621 * @param types bitmask of data types; 0 is treated as a "don't care" 622 * @return Array of topic information. 623 */ 624 public Topic[] getTopics(String prefix, int types) { 625 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, prefix, types)); 626 } 627 628 /** 629 * Get published topics starting with the given prefix. The results are optionally filtered by 630 * string prefix and data type to only return a subset of all topics. 631 * 632 * @param prefix topic name required prefix; only topics whose name starts with this string are 633 * returned 634 * @param types array of data type strings 635 * @return Array of topic information. 636 */ 637 public Topic[] getTopics(String prefix, String[] types) { 638 return topicHandlesToTopics(NetworkTablesJNI.getTopicsStr(m_handle, prefix, types)); 639 } 640 641 /** 642 * Get information about all topics. 643 * 644 * @return Array of topic information. 645 */ 646 public TopicInfo[] getTopicInfo() { 647 return NetworkTablesJNI.getTopicInfos(this, m_handle, "", 0); 648 } 649 650 /** 651 * Get information about topics starting with the given prefix. The results are optionally 652 * filtered by string prefix to only return a subset of all topics. 653 * 654 * @param prefix topic name required prefix; only topics whose name starts with this string are 655 * returned 656 * @return Array of topic information. 657 */ 658 public TopicInfo[] getTopicInfo(String prefix) { 659 return NetworkTablesJNI.getTopicInfos(this, m_handle, prefix, 0); 660 } 661 662 /** 663 * Get information about topics starting with the given prefix. The results are optionally 664 * filtered by string prefix and data type to only return a subset of all topics. 665 * 666 * @param prefix topic name required prefix; only topics whose name starts with this string are 667 * returned 668 * @param types bitmask of data types; 0 is treated as a "don't care" 669 * @return Array of topic information. 670 */ 671 public TopicInfo[] getTopicInfo(String prefix, int types) { 672 return NetworkTablesJNI.getTopicInfos(this, m_handle, prefix, types); 673 } 674 675 /** 676 * Get information about topics starting with the given prefix. The results are optionally 677 * filtered by string prefix and data type to only return a subset of all topics. 678 * 679 * @param prefix topic name required prefix; only topics whose name starts with this string are 680 * returned 681 * @param types array of data type strings 682 * @return Array of topic information. 683 */ 684 public TopicInfo[] getTopicInfo(String prefix, String[] types) { 685 return NetworkTablesJNI.getTopicInfosStr(this, m_handle, prefix, types); 686 } 687 688 /* Cache of created entries. */ 689 private final ConcurrentMap<String, NetworkTableEntry> m_entries = new ConcurrentHashMap<>(); 690 691 /** 692 * Gets the entry for a key. 693 * 694 * @param name Key 695 * @return Network table entry. 696 */ 697 public NetworkTableEntry getEntry(String name) { 698 NetworkTableEntry entry = m_entries.get(name); 699 if (entry == null) { 700 entry = new NetworkTableEntry(this, NetworkTablesJNI.getEntry(m_handle, name)); 701 NetworkTableEntry oldEntry = m_entries.putIfAbsent(name, entry); 702 if (oldEntry != null) { 703 entry = oldEntry; 704 } 705 } 706 return entry; 707 } 708 709 /* Cache of created topics. */ 710 private final ConcurrentMap<String, Topic> m_topics = new ConcurrentHashMap<>(); 711 private final ConcurrentMap<Integer, Topic> m_topicsByHandle = new ConcurrentHashMap<>(); 712 713 Topic getCachedTopic(String name) { 714 Topic topic = m_topics.get(name); 715 if (topic == null) { 716 int handle = NetworkTablesJNI.getTopic(m_handle, name); 717 topic = new Topic(this, handle); 718 Topic oldTopic = m_topics.putIfAbsent(name, topic); 719 if (oldTopic != null) { 720 topic = oldTopic; 721 } 722 // also cache by handle 723 m_topicsByHandle.putIfAbsent(handle, topic); 724 } 725 return topic; 726 } 727 728 Topic getCachedTopic(int handle) { 729 Topic topic = m_topicsByHandle.get(handle); 730 if (topic == null) { 731 topic = new Topic(this, handle); 732 Topic oldTopic = m_topicsByHandle.putIfAbsent(handle, topic); 733 if (oldTopic != null) { 734 topic = oldTopic; 735 } 736 } 737 return topic; 738 } 739 740 /* Cache of created tables. */ 741 private final ConcurrentMap<String, NetworkTable> m_tables = new ConcurrentHashMap<>(); 742 743 /** 744 * Gets the table with the specified key. 745 * 746 * @param key the key name 747 * @return The network table 748 */ 749 public NetworkTable getTable(String key) { 750 // prepend leading / if not present 751 String theKey; 752 if (key.isEmpty() || "/".equals(key)) { 753 theKey = ""; 754 } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) { 755 theKey = key; 756 } else { 757 theKey = NetworkTable.PATH_SEPARATOR + key; 758 } 759 760 // cache created tables 761 NetworkTable table = m_tables.get(theKey); 762 if (table == null) { 763 table = new NetworkTable(this, theKey); 764 NetworkTable oldTable = m_tables.putIfAbsent(theKey, table); 765 if (oldTable != null) { 766 table = oldTable; 767 } 768 } 769 return table; 770 } 771 772 /* 773 * Callback Creation Functions 774 */ 775 776 private static class ListenerStorage implements AutoCloseable { 777 private final ReentrantLock m_lock = new ReentrantLock(); 778 private final Map<Integer, Consumer<NetworkTableEvent>> m_listeners = new HashMap<>(); 779 780 @SuppressWarnings("PMD.SingularField") 781 private Thread m_thread; 782 783 private int m_poller; 784 private boolean m_waitQueue; 785 private final Event m_waitQueueEvent = new Event(); 786 private final Condition m_waitQueueCond = m_lock.newCondition(); 787 private final NetworkTableInstance m_inst; 788 789 ListenerStorage(NetworkTableInstance inst) { 790 m_inst = inst; 791 } 792 793 int add( 794 String[] prefixes, 795 EnumSet<NetworkTableEvent.Kind> eventKinds, 796 Consumer<NetworkTableEvent> listener) { 797 m_lock.lock(); 798 try { 799 if (m_poller == 0) { 800 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 801 startThread(); 802 } 803 int h = NetworkTablesJNI.addListener(m_poller, prefixes, eventKinds); 804 m_listeners.put(h, listener); 805 return h; 806 } finally { 807 m_lock.unlock(); 808 } 809 } 810 811 int add( 812 int handle, 813 EnumSet<NetworkTableEvent.Kind> eventKinds, 814 Consumer<NetworkTableEvent> listener) { 815 m_lock.lock(); 816 try { 817 if (m_poller == 0) { 818 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 819 startThread(); 820 } 821 int h = NetworkTablesJNI.addListener(m_poller, handle, eventKinds); 822 m_listeners.put(h, listener); 823 return h; 824 } finally { 825 m_lock.unlock(); 826 } 827 } 828 829 int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) { 830 m_lock.lock(); 831 try { 832 if (m_poller == 0) { 833 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 834 startThread(); 835 } 836 int h = NetworkTablesJNI.addLogger(m_poller, minLevel, maxLevel); 837 m_listeners.put(h, listener); 838 return h; 839 } finally { 840 m_lock.unlock(); 841 } 842 } 843 844 void remove(int listener) { 845 m_lock.lock(); 846 try { 847 m_listeners.remove(listener); 848 } finally { 849 m_lock.unlock(); 850 } 851 NetworkTablesJNI.removeListener(listener); 852 } 853 854 @Override 855 public void close() { 856 if (m_poller != 0) { 857 NetworkTablesJNI.destroyListenerPoller(m_poller); 858 } 859 m_poller = 0; 860 } 861 862 private void startThread() { 863 m_thread = 864 new Thread( 865 () -> { 866 boolean wasInterrupted = false; 867 int[] handles = new int[] { m_poller, m_waitQueueEvent.getHandle() }; 868 while (!Thread.interrupted()) { 869 try { 870 WPIUtilJNI.waitForObjects(handles); 871 } catch (InterruptedException ex) { 872 m_lock.lock(); 873 try { 874 if (m_waitQueue) { 875 m_waitQueue = false; 876 m_waitQueueCond.signalAll(); 877 } 878 } finally { 879 m_lock.unlock(); 880 } 881 Thread.currentThread().interrupt(); 882 // don't try to destroy poller, as its handle is likely no longer valid 883 wasInterrupted = true; 884 break; 885 } 886 for (NetworkTableEvent event : 887 NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) { 888 Consumer<NetworkTableEvent> listener; 889 m_lock.lock(); 890 try { 891 listener = m_listeners.get(event.listener); 892 } finally { 893 m_lock.unlock(); 894 } 895 if (listener != null) { 896 try { 897 listener.accept(event); 898 } catch (Throwable throwable) { 899 System.err.println( 900 "Unhandled exception during listener callback: " 901 + throwable.toString()); 902 throwable.printStackTrace(); 903 } 904 } 905 } 906 m_lock.lock(); 907 try { 908 if (m_waitQueue) { 909 m_waitQueue = false; 910 m_waitQueueCond.signalAll(); 911 } 912 } finally { 913 m_lock.unlock(); 914 } 915 } 916 m_lock.lock(); 917 try { 918 if (!wasInterrupted) { 919 NetworkTablesJNI.destroyListenerPoller(m_poller); 920 } 921 m_poller = 0; 922 } finally { 923 m_lock.unlock(); 924 } 925 }, 926 "NTListener"); 927 m_thread.setDaemon(true); 928 m_thread.start(); 929 } 930 931 boolean waitForQueue(double timeout) { 932 m_lock.lock(); 933 try { 934 if (m_poller != 0) { 935 m_waitQueue = true; 936 m_waitQueueEvent.set(); 937 while (m_waitQueue) { 938 try { 939 if (timeout < 0) { 940 m_waitQueueCond.await(); 941 } else { 942 return m_waitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); 943 } 944 } catch (InterruptedException ex) { 945 Thread.currentThread().interrupt(); 946 return true; 947 } 948 } 949 } 950 } finally { 951 m_lock.unlock(); 952 } 953 return true; 954 } 955 } 956 957 private final ListenerStorage m_listeners = new ListenerStorage(this); 958 959 /** 960 * Remove a connection listener. 961 * 962 * @param listener Listener handle to remove 963 */ 964 public void removeListener(int listener) { 965 m_listeners.remove(listener); 966 } 967 968 /** 969 * Wait for the listener queue to be empty. This is primarily useful for deterministic 970 * testing. This blocks until either the listener queue is empty (e.g. there are no 971 * more events that need to be passed along to callbacks or poll queues) or the timeout expires. 972 * 973 * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to 974 * block indefinitely 975 * @return False if timed out, otherwise true. 976 */ 977 public boolean waitForListenerQueue(double timeout) { 978 return m_listeners.waitForQueue(timeout); 979 } 980 981 /** 982 * Add a connection listener. The callback function is called asynchronously on a separate 983 * thread, so it's important to use synchronization or atomics when accessing any shared state 984 * from the callback function. 985 * 986 * @param immediateNotify Notify listener of all existing connections 987 * @param listener Listener to add 988 * @return Listener handle 989 */ 990 public int addConnectionListener( 991 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 992 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection); 993 if (immediateNotify) { 994 eventKinds.add(NetworkTableEvent.Kind.kImmediate); 995 } 996 return m_listeners.add(m_handle, eventKinds, listener); 997 } 998 999 /** 1000 * Add a time synchronization listener. The callback function is called asynchronously on a 1001 * separate thread, so it's important to use synchronization or atomics when accessing any shared 1002 * state from the callback function. 1003 * 1004 * @param immediateNotify Notify listener of current time synchronization value 1005 * @param listener Listener to add 1006 * @return Listener handle 1007 */ 1008 public int addTimeSyncListener( 1009 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 1010 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kTimeSync); 1011 if (immediateNotify) { 1012 eventKinds.add(NetworkTableEvent.Kind.kImmediate); 1013 } 1014 return m_listeners.add(m_handle, eventKinds, listener); 1015 } 1016 1017 /** 1018 * Add a listener for changes on a particular topic. The callback function is called 1019 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1020 * accessing any shared state from the callback function. 1021 * 1022 * <p>This creates a corresponding internal subscriber with the lifetime of the 1023 * listener. 1024 * 1025 * @param topic Topic 1026 * @param eventKinds set of event kinds to listen to 1027 * @param listener Listener function 1028 * @return Listener handle 1029 */ 1030 public int addListener( 1031 Topic topic, 1032 EnumSet<NetworkTableEvent.Kind> eventKinds, 1033 Consumer<NetworkTableEvent> listener) { 1034 if (topic.getInstance().getHandle() != m_handle) { 1035 throw new IllegalArgumentException("topic is not from this instance"); 1036 } 1037 return m_listeners.add(topic.getHandle(), eventKinds, listener); 1038 } 1039 1040 /** 1041 * Add a listener for changes on a subscriber. The callback function is called 1042 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1043 * accessing any shared state from the callback function. This does NOT keep the subscriber 1044 * active. 1045 * 1046 * @param subscriber Subscriber 1047 * @param eventKinds set of event kinds to listen to 1048 * @param listener Listener function 1049 * @return Listener handle 1050 */ 1051 public int addListener( 1052 Subscriber subscriber, 1053 EnumSet<NetworkTableEvent.Kind> eventKinds, 1054 Consumer<NetworkTableEvent> listener) { 1055 if (subscriber.getTopic().getInstance().getHandle() != m_handle) { 1056 throw new IllegalArgumentException("subscriber is not from this instance"); 1057 } 1058 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 1059 } 1060 1061 /** 1062 * Add a listener for changes on a subscriber. The callback function is called 1063 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1064 * accessing any shared state from the callback function. This does NOT keep the subscriber 1065 * active. 1066 * 1067 * @param subscriber Subscriber 1068 * @param eventKinds set of event kinds to listen to 1069 * @param listener Listener function 1070 * @return Listener handle 1071 */ 1072 public int addListener( 1073 MultiSubscriber subscriber, 1074 EnumSet<NetworkTableEvent.Kind> eventKinds, 1075 Consumer<NetworkTableEvent> listener) { 1076 if (subscriber.getInstance().getHandle() != m_handle) { 1077 throw new IllegalArgumentException("subscriber is not from this instance"); 1078 } 1079 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 1080 } 1081 1082 /** 1083 * Add a listener for changes on an entry. The callback function is called 1084 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1085 * accessing any shared state from the callback function. 1086 * 1087 * @param entry Entry 1088 * @param eventKinds set of event kinds to listen to 1089 * @param listener Listener function 1090 * @return Listener handle 1091 */ 1092 public int addListener( 1093 NetworkTableEntry entry, 1094 EnumSet<NetworkTableEvent.Kind> eventKinds, 1095 Consumer<NetworkTableEvent> listener) { 1096 if (entry.getTopic().getInstance().getHandle() != m_handle) { 1097 throw new IllegalArgumentException("entry is not from this instance"); 1098 } 1099 return m_listeners.add(entry.getHandle(), eventKinds, listener); 1100 } 1101 1102 /** 1103 * Add a listener for changes to topics with names that start with any of the given 1104 * prefixes. The callback function is called asynchronously on a separate thread, so it's 1105 * important to use synchronization or atomics when accessing any shared state from the callback 1106 * function. 1107 * 1108 * <p>This creates a corresponding internal subscriber with the lifetime of the 1109 * listener. 1110 * 1111 * @param prefixes Topic name string prefixes 1112 * @param eventKinds set of event kinds to listen to 1113 * @param listener Listener function 1114 * @return Listener handle 1115 */ 1116 public int addListener( 1117 String[] prefixes, 1118 EnumSet<NetworkTableEvent.Kind> eventKinds, 1119 Consumer<NetworkTableEvent> listener) { 1120 return m_listeners.add(prefixes, eventKinds, listener); 1121 } 1122 1123 /* 1124 * Client/Server Functions 1125 */ 1126 1127 /** 1128 * Get the current network mode. 1129 * 1130 * @return Enum set of NetworkMode. 1131 */ 1132 public EnumSet<NetworkMode> getNetworkMode() { 1133 int flags = NetworkTablesJNI.getNetworkMode(m_handle); 1134 EnumSet<NetworkMode> rv = EnumSet.noneOf(NetworkMode.class); 1135 for (NetworkMode mode : NetworkMode.values()) { 1136 if ((flags & mode.getValue()) != 0) { 1137 rv.add(mode); 1138 } 1139 } 1140 return rv; 1141 } 1142 1143 /** 1144 * Starts local-only operation. Prevents calls to startServer or startClient from taking effect. 1145 * Has no effect if startServer or startClient has already been called. 1146 */ 1147 public void startLocal() { 1148 NetworkTablesJNI.startLocal(m_handle); 1149 } 1150 1151 /** 1152 * Stops local-only operation. startServer or startClient can be called after this call to start 1153 * a server or client. 1154 */ 1155 public void stopLocal() { 1156 NetworkTablesJNI.stopLocal(m_handle); 1157 } 1158 1159 /** 1160 * Starts a server using the networktables.json as the persistent file, using the default 1161 * listening address and port. 1162 */ 1163 public void startServer() { 1164 startServer("networktables.json"); 1165 } 1166 1167 /** 1168 * Starts a server using the specified persistent filename, using the default listening address 1169 * and port. 1170 * 1171 * @param persistFilename the name of the persist file to use 1172 */ 1173 public void startServer(String persistFilename) { 1174 startServer(persistFilename, ""); 1175 } 1176 1177 /** 1178 * Starts a server using the specified filename and listening address, using the default port. 1179 * 1180 * @param persistFilename the name of the persist file to use 1181 * @param listenAddress the address to listen on, or empty to listen on any address 1182 */ 1183 public void startServer(String persistFilename, String listenAddress) { 1184 startServer(persistFilename, listenAddress, kDefaultPort); 1185 } 1186 1187 /** 1188 * Starts a server using the specified filename, listening address, and port. 1189 * 1190 * @param persistFilename the name of the persist file to use 1191 * @param listenAddress the address to listen on, or empty to listen on any address 1192 * @param port port to communicate over 1193 */ 1194 public void startServer(String persistFilename, String listenAddress, int port) { 1195 NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port); 1196 } 1197 1198 /** Stops the server if it is running. */ 1199 public void stopServer() { 1200 NetworkTablesJNI.stopServer(m_handle); 1201 } 1202 1203 /** 1204 * Starts a client. Use SetServer or SetServerTeam to set the server name and port. 1205 * 1206 * @param identity network identity to advertise (cannot be empty string) 1207 */ 1208 public void startClient(String identity) { 1209 NetworkTablesJNI.startClient(m_handle, identity); 1210 } 1211 1212 /** Stops the client if it is running. */ 1213 public void stopClient() { 1214 NetworkTablesJNI.stopClient(m_handle); 1215 } 1216 1217 /** 1218 * Sets server address and port for client (without restarting client). Changes the port to the 1219 * default port. 1220 * 1221 * @param serverName server name 1222 */ 1223 public void setServer(String serverName) { 1224 setServer(serverName, 0); 1225 } 1226 1227 /** 1228 * Sets server address and port for client (without restarting client). 1229 * 1230 * @param serverName server name 1231 * @param port port to communicate over (0=default) 1232 */ 1233 public void setServer(String serverName, int port) { 1234 NetworkTablesJNI.setServer(m_handle, serverName, port); 1235 } 1236 1237 /** 1238 * Sets server addresses and port for client (without restarting client). Changes the port to the 1239 * default port. The client will attempt to connect to each server in round robin fashion. 1240 * 1241 * @param serverNames array of server names 1242 */ 1243 public void setServer(String[] serverNames) { 1244 setServer(serverNames, 0); 1245 } 1246 1247 /** 1248 * Sets server addresses and port for client (without restarting client). The client will attempt 1249 * to connect to each server in round robin fashion. 1250 * 1251 * @param serverNames array of server names 1252 * @param port port to communicate over (0=default) 1253 */ 1254 public void setServer(String[] serverNames, int port) { 1255 int[] ports = new int[serverNames.length]; 1256 for (int i = 0; i < serverNames.length; i++) { 1257 ports[i] = port; 1258 } 1259 setServer(serverNames, ports); 1260 } 1261 1262 /** 1263 * Sets server addresses and ports for client (without restarting client). The client will 1264 * attempt to connect to each server in round robin fashion. 1265 * 1266 * @param serverNames array of server names 1267 * @param ports array of port numbers (0=default) 1268 */ 1269 public void setServer(String[] serverNames, int[] ports) { 1270 NetworkTablesJNI.setServer(m_handle, serverNames, ports); 1271 } 1272 1273 /** 1274 * Sets server addresses and port for client (without restarting client). Changes the port to the 1275 * default port. The client will attempt to connect to each server in round robin fashion. 1276 * 1277 * @param team team number 1278 */ 1279 public void setServerTeam(int team) { 1280 setServerTeam(team, 0); 1281 } 1282 1283 /** 1284 * Sets server addresses and port for client (without restarting client). Connects using commonly 1285 * known robot addresses for the specified team. 1286 * 1287 * @param team team number 1288 * @param port port to communicate over (0=default) 1289 */ 1290 public void setServerTeam(int team, int port) { 1291 NetworkTablesJNI.setServerTeam(m_handle, team, port); 1292 } 1293 1294 /** 1295 * Disconnects the client if it's running and connected. This will automatically start 1296 * reconnection attempts to the current server list. 1297 */ 1298 public void disconnect() { 1299 NetworkTablesJNI.disconnect(m_handle); 1300 } 1301 1302 /** 1303 * Starts requesting server address from Driver Station. This connects to the Driver Station 1304 * running on localhost to obtain the server IP address, and connects with the default port. 1305 */ 1306 public void startDSClient() { 1307 startDSClient(0); 1308 } 1309 1310 /** 1311 * Starts requesting server address from Driver Station. This connects to the Driver Station 1312 * running on localhost to obtain the server IP address. 1313 * 1314 * @param port server port to use in combination with IP from DS (0=default) 1315 */ 1316 public void startDSClient(int port) { 1317 NetworkTablesJNI.startDSClient(m_handle, port); 1318 } 1319 1320 /** Stops requesting server address from Driver Station. */ 1321 public void stopDSClient() { 1322 NetworkTablesJNI.stopDSClient(m_handle); 1323 } 1324 1325 /** 1326 * Flushes all updated values immediately to the local client/server. This does not flush to the 1327 * network. 1328 */ 1329 public void flushLocal() { 1330 NetworkTablesJNI.flushLocal(m_handle); 1331 } 1332 1333 /** 1334 * Flushes all updated values immediately to the network. Note: This is rate-limited to protect 1335 * the network from flooding. This is primarily useful for synchronizing network updates with 1336 * user code. 1337 */ 1338 public void flush() { 1339 NetworkTablesJNI.flush(m_handle); 1340 } 1341 1342 /** 1343 * Gets information on the currently established network connections. If operating as a client, 1344 * this will return either zero or one values. 1345 * 1346 * @return array of connection information 1347 */ 1348 public ConnectionInfo[] getConnections() { 1349 return NetworkTablesJNI.getConnections(m_handle); 1350 } 1351 1352 /** 1353 * Return whether or not the instance is connected to another node. 1354 * 1355 * @return True if connected. 1356 */ 1357 public boolean isConnected() { 1358 return NetworkTablesJNI.isConnected(m_handle); 1359 } 1360 1361 /** 1362 * Get the time offset between server time and local time. Add this value to local time to get 1363 * the estimated equivalent server time. In server mode, this always returns 0. In client mode, 1364 * this returns the time offset only if the client and server are connected and have exchanged 1365 * synchronization messages. Note the time offset may change over time as it is periodically 1366 * updated; to receive updates as events, add a listener to the "time sync" event. 1367 * 1368 * @return Time offset in microseconds (optional) 1369 */ 1370 public OptionalLong getServerTimeOffset() { 1371 return NetworkTablesJNI.getServerTimeOffset(m_handle); 1372 } 1373 1374 /** 1375 * Starts logging entry changes to a DataLog. 1376 * 1377 * @param log data log object; lifetime must extend until StopEntryDataLog is called or the 1378 * instance is destroyed 1379 * @param prefix only store entries with names that start with this prefix; the prefix is not 1380 * included in the data log entry name 1381 * @param logPrefix prefix to add to data log entry names 1382 * @return Data logger handle 1383 */ 1384 public int startEntryDataLog(DataLog log, String prefix, String logPrefix) { 1385 return NetworkTablesJNI.startEntryDataLog(m_handle, log, prefix, logPrefix); 1386 } 1387 1388 /** 1389 * Stops logging entry changes to a DataLog. 1390 * 1391 * @param logger data logger handle 1392 */ 1393 public static void stopEntryDataLog(int logger) { 1394 NetworkTablesJNI.stopEntryDataLog(logger); 1395 } 1396 1397 /** 1398 * Starts logging connection changes to a DataLog. 1399 * 1400 * @param log data log object; lifetime must extend until StopConnectionDataLog is called or the 1401 * instance is destroyed 1402 * @param name data log entry name 1403 * @return Data logger handle 1404 */ 1405 public int startConnectionDataLog(DataLog log, String name) { 1406 return NetworkTablesJNI.startConnectionDataLog(m_handle, log, name); 1407 } 1408 1409 /** 1410 * Stops logging connection changes to a DataLog. 1411 * 1412 * @param logger data logger handle 1413 */ 1414 public static void stopConnectionDataLog(int logger) { 1415 NetworkTablesJNI.stopConnectionDataLog(logger); 1416 } 1417 1418 /** 1419 * Add logger callback function. By default, log messages are sent to stderr; this function sends 1420 * log messages with the specified levels to the provided callback function instead. The callback 1421 * function will only be called for log messages with level greater than or equal to minLevel and 1422 * less than or equal to maxLevel; messages outside this range will be silently ignored. 1423 * 1424 * @param minLevel minimum log level 1425 * @param maxLevel maximum log level 1426 * @param func callback function 1427 * @return Listener handle 1428 */ 1429 public int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> func) { 1430 return m_listeners.addLogger(minLevel, maxLevel, func); 1431 } 1432 1433 /** 1434 * Returns whether there is a data schema already registered with the given name that this 1435 * instance has published. This does NOT perform a check as to whether the schema has already 1436 * been published by another node on the network. 1437 * 1438 * @param name Name (the string passed as the data type for topics using this schema) 1439 * @return True if schema already registered 1440 */ 1441 public boolean hasSchema(String name) { 1442 return m_schemas.containsKey("/.schema/" + name); 1443 } 1444 1445 /** 1446 * Registers a data schema. Data schemas provide information for how a certain data type string 1447 * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. 1448 * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas 1449 * are published just like normal topics, with the name being generated from the provided name: 1450 * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. 1451 * 1452 * @param name Name (the string passed as the data type for topics using this schema) 1453 * @param type Type of schema (e.g. "protobuf", "struct", etc) 1454 * @param schema Schema data 1455 */ 1456 public void addSchema(String name, String type, byte[] schema) { 1457 m_schemas.computeIfAbsent("/.schema/" + name, k -> { 1458 RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}"); 1459 pub.setDefault(schema); 1460 return pub; 1461 }); 1462 } 1463 1464 /** 1465 * Registers a data schema. Data schemas provide information for how a certain data type string 1466 * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. 1467 * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas 1468 * are published just like normal topics, with the name being generated from the provided name: 1469 * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. 1470 * 1471 * @param name Name (the string passed as the data type for topics using this schema) 1472 * @param type Type of schema (e.g. "protobuf", "struct", etc) 1473 * @param schema Schema data 1474 */ 1475 public void addSchema(String name, String type, String schema) { 1476 m_schemas.computeIfAbsent("/.schema/" + name, k -> { 1477 RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}"); 1478 pub.setDefault(StandardCharsets.UTF_8.encode(schema)); 1479 return pub; 1480 }); 1481 } 1482 1483 /** 1484 * Registers a protobuf schema. Duplicate calls to this function with the same name are silently 1485 * ignored. 1486 * 1487 * @param proto protobuf serialization object 1488 */ 1489 public void addSchema(Protobuf<?, ?> proto) { 1490 proto.forEachDescriptor( 1491 this::hasSchema, 1492 (typeString, schema) -> addSchema(typeString, "proto:FileDescriptorProto", schema)); 1493 } 1494 1495 /** 1496 * Registers a struct schema. Duplicate calls to this function with the same name are silently 1497 * ignored. 1498 * 1499 * @param struct struct serialization object 1500 */ 1501 public void addSchema(Struct<?> struct) { 1502 addSchemaImpl(struct, new HashSet<>()); 1503 } 1504 1505 @Override 1506 public boolean equals(Object other) { 1507 return other == this || other instanceof NetworkTableInstance inst && m_handle == inst.m_handle; 1508 } 1509 1510 @Override 1511 public int hashCode() { 1512 return m_handle; 1513 } 1514 1515 private void addSchemaImpl(Struct<?> struct, Set<String> seen) { 1516 String typeString = struct.getTypeString(); 1517 if (hasSchema(typeString)) { 1518 return; 1519 } 1520 if (!seen.add(typeString)) { 1521 throw new UnsupportedOperationException(typeString + ": circular reference with " + seen); 1522 } 1523 addSchema(typeString, "structschema", struct.getSchema()); 1524 for (Struct<?> inner : struct.getNested()) { 1525 addSchemaImpl(inner, seen); 1526 } 1527 seen.remove(typeString); 1528 } 1529 1530 private boolean m_owned; 1531 private int m_handle; 1532 private final ConcurrentMap<String, RawPublisher> m_schemas = new ConcurrentHashMap<>(); 1533}