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 org.wpilib.networktables; 008 009import java.nio.charset.StandardCharsets; 010import java.util.EnumSet; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.Map; 014import java.util.OptionalLong; 015import java.util.Set; 016import java.util.concurrent.ConcurrentHashMap; 017import java.util.concurrent.ConcurrentMap; 018import java.util.concurrent.TimeUnit; 019import java.util.concurrent.locks.Condition; 020import java.util.concurrent.locks.ReentrantLock; 021import java.util.function.Consumer; 022import org.wpilib.datalog.DataLog; 023import org.wpilib.util.WPIUtilJNI; 024import org.wpilib.util.concurrent.Event; 025import org.wpilib.util.protobuf.Protobuf; 026import org.wpilib.util.struct.Struct; 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 SERVER(0x01), 051 052 /** Running in client mode. */ 053 CLIENT(0x04), 054 055 /** Currently starting up (either client or server). */ 056 STARTING(0x08), 057 058 /** Running in local-only mode. */ 059 LOCAL(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 DEFAULT_PORT = 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 @SuppressWarnings("PMD.AvoidCatchingGenericException") 863 private void startThread() { 864 m_thread = 865 new Thread( 866 () -> { 867 boolean wasInterrupted = false; 868 int[] handles = new int[] { m_poller, m_waitQueueEvent.getHandle() }; 869 while (!Thread.interrupted()) { 870 try { 871 WPIUtilJNI.waitForObjects(handles); 872 } catch (InterruptedException ex) { 873 m_lock.lock(); 874 try { 875 if (m_waitQueue) { 876 m_waitQueue = false; 877 m_waitQueueCond.signalAll(); 878 } 879 } finally { 880 m_lock.unlock(); 881 } 882 Thread.currentThread().interrupt(); 883 // don't try to destroy poller, as its handle is likely no longer valid 884 wasInterrupted = true; 885 break; 886 } 887 for (NetworkTableEvent event : 888 NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) { 889 Consumer<NetworkTableEvent> listener; 890 m_lock.lock(); 891 try { 892 listener = m_listeners.get(event.listener); 893 } finally { 894 m_lock.unlock(); 895 } 896 if (listener != null) { 897 try { 898 listener.accept(event); 899 } catch (Throwable throwable) { 900 System.err.println( 901 "Unhandled exception during listener callback: " 902 + throwable.toString()); 903 throwable.printStackTrace(); 904 } 905 } 906 } 907 m_lock.lock(); 908 try { 909 if (m_waitQueue) { 910 m_waitQueue = false; 911 m_waitQueueCond.signalAll(); 912 } 913 } finally { 914 m_lock.unlock(); 915 } 916 } 917 m_lock.lock(); 918 try { 919 if (!wasInterrupted) { 920 NetworkTablesJNI.destroyListenerPoller(m_poller); 921 } 922 m_poller = 0; 923 } finally { 924 m_lock.unlock(); 925 } 926 }, 927 "NTListener"); 928 m_thread.setDaemon(true); 929 m_thread.start(); 930 } 931 932 boolean waitForQueue(double timeout) { 933 m_lock.lock(); 934 try { 935 if (m_poller != 0) { 936 m_waitQueue = true; 937 m_waitQueueEvent.set(); 938 while (m_waitQueue) { 939 try { 940 if (timeout < 0) { 941 m_waitQueueCond.await(); 942 } else { 943 return m_waitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); 944 } 945 } catch (InterruptedException ex) { 946 Thread.currentThread().interrupt(); 947 return true; 948 } 949 } 950 } 951 } finally { 952 m_lock.unlock(); 953 } 954 return true; 955 } 956 } 957 958 private final ListenerStorage m_listeners = new ListenerStorage(this); 959 960 /** 961 * Remove a connection listener. 962 * 963 * @param listener Listener handle to remove 964 */ 965 public void removeListener(int listener) { 966 m_listeners.remove(listener); 967 } 968 969 /** 970 * Wait for the listener queue to be empty. This is primarily useful for deterministic 971 * testing. This blocks until either the listener queue is empty (e.g. there are no 972 * more events that need to be passed along to callbacks or poll queues) or the timeout expires. 973 * 974 * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to 975 * block indefinitely 976 * @return False if timed out, otherwise true. 977 */ 978 public boolean waitForListenerQueue(double timeout) { 979 return m_listeners.waitForQueue(timeout); 980 } 981 982 /** 983 * Add a connection listener. The callback function is called asynchronously on a separate 984 * thread, so it's important to use synchronization or atomics when accessing any shared state 985 * from the callback function. 986 * 987 * @param immediateNotify Notify listener of all existing connections 988 * @param listener Listener to add 989 * @return Listener handle 990 */ 991 public int addConnectionListener( 992 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 993 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.CONNECTION); 994 if (immediateNotify) { 995 eventKinds.add(NetworkTableEvent.Kind.IMMEDIATE); 996 } 997 return m_listeners.add(m_handle, eventKinds, listener); 998 } 999 1000 /** 1001 * Add a time synchronization listener. The callback function is called asynchronously on a 1002 * separate thread, so it's important to use synchronization or atomics when accessing any shared 1003 * state from the callback function. 1004 * 1005 * @param immediateNotify Notify listener of current time synchronization value 1006 * @param listener Listener to add 1007 * @return Listener handle 1008 */ 1009 public int addTimeSyncListener( 1010 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 1011 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.TIME_SYNC); 1012 if (immediateNotify) { 1013 eventKinds.add(NetworkTableEvent.Kind.IMMEDIATE); 1014 } 1015 return m_listeners.add(m_handle, eventKinds, listener); 1016 } 1017 1018 /** 1019 * Add a listener for changes on a particular topic. The callback function is called 1020 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1021 * accessing any shared state from the callback function. 1022 * 1023 * <p>This creates a corresponding internal subscriber with the lifetime of the 1024 * listener. 1025 * 1026 * @param topic Topic 1027 * @param eventKinds set of event kinds to listen to 1028 * @param listener Listener function 1029 * @return Listener handle 1030 */ 1031 public int addListener( 1032 Topic topic, 1033 EnumSet<NetworkTableEvent.Kind> eventKinds, 1034 Consumer<NetworkTableEvent> listener) { 1035 if (topic.getInstance().getHandle() != m_handle) { 1036 throw new IllegalArgumentException("topic is not from this instance"); 1037 } 1038 return m_listeners.add(topic.getHandle(), eventKinds, listener); 1039 } 1040 1041 /** 1042 * Add a listener for changes on a subscriber. The callback function is called 1043 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1044 * accessing any shared state from the callback function. This does NOT keep the subscriber 1045 * active. 1046 * 1047 * @param subscriber Subscriber 1048 * @param eventKinds set of event kinds to listen to 1049 * @param listener Listener function 1050 * @return Listener handle 1051 */ 1052 public int addListener( 1053 Subscriber subscriber, 1054 EnumSet<NetworkTableEvent.Kind> eventKinds, 1055 Consumer<NetworkTableEvent> listener) { 1056 if (subscriber.getTopic().getInstance().getHandle() != m_handle) { 1057 throw new IllegalArgumentException("subscriber is not from this instance"); 1058 } 1059 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 1060 } 1061 1062 /** 1063 * Add a listener for changes on a subscriber. The callback function is called 1064 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1065 * accessing any shared state from the callback function. This does NOT keep the subscriber 1066 * active. 1067 * 1068 * @param subscriber Subscriber 1069 * @param eventKinds set of event kinds to listen to 1070 * @param listener Listener function 1071 * @return Listener handle 1072 */ 1073 public int addListener( 1074 MultiSubscriber subscriber, 1075 EnumSet<NetworkTableEvent.Kind> eventKinds, 1076 Consumer<NetworkTableEvent> listener) { 1077 if (subscriber.getInstance().getHandle() != m_handle) { 1078 throw new IllegalArgumentException("subscriber is not from this instance"); 1079 } 1080 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 1081 } 1082 1083 /** 1084 * Add a listener for changes on an entry. The callback function is called 1085 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1086 * accessing any shared state from the callback function. 1087 * 1088 * @param entry Entry 1089 * @param eventKinds set of event kinds to listen to 1090 * @param listener Listener function 1091 * @return Listener handle 1092 */ 1093 public int addListener( 1094 NetworkTableEntry entry, 1095 EnumSet<NetworkTableEvent.Kind> eventKinds, 1096 Consumer<NetworkTableEvent> listener) { 1097 if (entry.getTopic().getInstance().getHandle() != m_handle) { 1098 throw new IllegalArgumentException("entry is not from this instance"); 1099 } 1100 return m_listeners.add(entry.getHandle(), eventKinds, listener); 1101 } 1102 1103 /** 1104 * Add a listener for changes to topics with names that start with any of the given 1105 * prefixes. The callback function is called asynchronously on a separate thread, so it's 1106 * important to use synchronization or atomics when accessing any shared state from the callback 1107 * function. 1108 * 1109 * <p>This creates a corresponding internal subscriber with the lifetime of the 1110 * listener. 1111 * 1112 * @param prefixes Topic name string prefixes 1113 * @param eventKinds set of event kinds to listen to 1114 * @param listener Listener function 1115 * @return Listener handle 1116 */ 1117 public int addListener( 1118 String[] prefixes, 1119 EnumSet<NetworkTableEvent.Kind> eventKinds, 1120 Consumer<NetworkTableEvent> listener) { 1121 return m_listeners.add(prefixes, eventKinds, listener); 1122 } 1123 1124 /* 1125 * Client/Server Functions 1126 */ 1127 1128 /** 1129 * Get the current network mode. 1130 * 1131 * @return Enum set of NetworkMode. 1132 */ 1133 public EnumSet<NetworkMode> getNetworkMode() { 1134 int flags = NetworkTablesJNI.getNetworkMode(m_handle); 1135 EnumSet<NetworkMode> rv = EnumSet.noneOf(NetworkMode.class); 1136 for (NetworkMode mode : NetworkMode.values()) { 1137 if ((flags & mode.getValue()) != 0) { 1138 rv.add(mode); 1139 } 1140 } 1141 return rv; 1142 } 1143 1144 /** 1145 * Starts local-only operation. Prevents calls to startServer or startClient from taking effect. 1146 * Has no effect if startServer or startClient has already been called. 1147 */ 1148 public void startLocal() { 1149 NetworkTablesJNI.startLocal(m_handle); 1150 } 1151 1152 /** 1153 * Stops local-only operation. startServer or startClient can be called after this call to start 1154 * a server or client. 1155 */ 1156 public void stopLocal() { 1157 NetworkTablesJNI.stopLocal(m_handle); 1158 } 1159 1160 /** 1161 * Starts a server using the networktables.json as the persistent file, using the default 1162 * listening address and port. 1163 */ 1164 public void startServer() { 1165 startServer("networktables.json"); 1166 } 1167 1168 /** 1169 * Starts a server using the specified persistent filename, using the default listening address 1170 * and port. 1171 * 1172 * @param persistFilename the name of the persist file to use 1173 */ 1174 public void startServer(String persistFilename) { 1175 startServer(persistFilename, "", ""); 1176 } 1177 1178 /** 1179 * Starts a server using the specified filename and listening address, using the default port. 1180 * 1181 * @param persistFilename the name of the persist file to use 1182 * @param listenAddress the address to listen on, or empty to listen on any address 1183 */ 1184 public void startServer(String persistFilename, String listenAddress) { 1185 startServer(persistFilename, listenAddress, ""); 1186 } 1187 1188 /** 1189 * Starts a server using the specified filename, listening address, and mdns service, 1190 * using the default port. 1191 * 1192 * @param persistFilename the name of the persist file to use 1193 * @param listenAddress the address to listen on, or empty to listen on any address 1194 * @param mdnsService the mDNS service name to advertise, or empty to not advertise 1195 */ 1196 public void startServer(String persistFilename, String listenAddress, String mdnsService) { 1197 startServer(persistFilename, listenAddress, mdnsService, DEFAULT_PORT); 1198 } 1199 1200 /** 1201 * Starts a server using the specified filename, listening address, mdns service and port. 1202 * 1203 * @param persistFilename the name of the persist file to use 1204 * @param listenAddress the address to listen on, or empty to listen on any address 1205 * @param mdnsService the mDNS service name to advertise, or empty to not advertise 1206 * @param port port to communicate over 1207 */ 1208 public void startServer(String persistFilename, String listenAddress, String mdnsService, 1209 int port) { 1210 NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, mdnsService, port); 1211 } 1212 1213 /** Stops the server if it is running. */ 1214 public void stopServer() { 1215 NetworkTablesJNI.stopServer(m_handle); 1216 } 1217 1218 /** 1219 * Starts a client. Use SetServer or SetServerTeam to set the server name and port. 1220 * 1221 * @param identity network identity to advertise (cannot be empty string) 1222 */ 1223 public void startClient(String identity) { 1224 NetworkTablesJNI.startClient(m_handle, identity); 1225 } 1226 1227 /** Stops the client if it is running. */ 1228 public void stopClient() { 1229 NetworkTablesJNI.stopClient(m_handle); 1230 } 1231 1232 /** 1233 * Sets server address and port for client (without restarting client). Changes the port to the 1234 * default port. 1235 * 1236 * @param serverName server name 1237 */ 1238 public void setServer(String serverName) { 1239 setServer(serverName, 0); 1240 } 1241 1242 /** 1243 * Sets server address and port for client (without restarting client). 1244 * 1245 * @param serverName server name 1246 * @param port port to communicate over (0=default) 1247 */ 1248 public void setServer(String serverName, int port) { 1249 NetworkTablesJNI.setServer(m_handle, serverName, port); 1250 } 1251 1252 /** 1253 * Sets server addresses and port for client (without restarting client). Changes the port to the 1254 * default port. The client will attempt to connect to each server in round robin fashion. 1255 * 1256 * @param serverNames array of server names 1257 */ 1258 public void setServer(String[] serverNames) { 1259 setServer(serverNames, 0); 1260 } 1261 1262 /** 1263 * Sets server addresses and port for client (without restarting client). The client will attempt 1264 * to connect to each server in round robin fashion. 1265 * 1266 * @param serverNames array of server names 1267 * @param port port to communicate over (0=default) 1268 */ 1269 public void setServer(String[] serverNames, int port) { 1270 int[] ports = new int[serverNames.length]; 1271 for (int i = 0; i < serverNames.length; i++) { 1272 ports[i] = port; 1273 } 1274 setServer(serverNames, ports); 1275 } 1276 1277 /** 1278 * Sets server addresses and ports for client (without restarting client). The client will 1279 * attempt to connect to each server in round robin fashion. 1280 * 1281 * @param serverNames array of server names 1282 * @param ports array of port numbers (0=default) 1283 */ 1284 public void setServer(String[] serverNames, int[] ports) { 1285 NetworkTablesJNI.setServer(m_handle, serverNames, ports); 1286 } 1287 1288 /** 1289 * Sets server addresses and port for client (without restarting client). Changes the port to the 1290 * default port. The client will attempt to connect to each server in round robin fashion. 1291 * 1292 * @param team team number 1293 */ 1294 public void setServerTeam(int team) { 1295 setServerTeam(team, 0); 1296 } 1297 1298 /** 1299 * Sets server addresses and port for client (without restarting client). Connects using commonly 1300 * known robot addresses for the specified team. 1301 * 1302 * @param team team number 1303 * @param port port to communicate over (0=default) 1304 */ 1305 public void setServerTeam(int team, int port) { 1306 NetworkTablesJNI.setServerTeam(m_handle, team, port); 1307 } 1308 1309 /** 1310 * Disconnects the client if it's running and connected. This will automatically start 1311 * reconnection attempts to the current server list. 1312 */ 1313 public void disconnect() { 1314 NetworkTablesJNI.disconnect(m_handle); 1315 } 1316 1317 /** 1318 * Starts requesting server address from Driver Station. This connects to the Driver Station 1319 * running on localhost to obtain the server IP address, and connects with the default port. 1320 */ 1321 public void startDSClient() { 1322 startDSClient(0); 1323 } 1324 1325 /** 1326 * Starts requesting server address from Driver Station. This connects to the Driver Station 1327 * running on localhost to obtain the server IP address. 1328 * 1329 * @param port server port to use in combination with IP from DS (0=default) 1330 */ 1331 public void startDSClient(int port) { 1332 NetworkTablesJNI.startDSClient(m_handle, port); 1333 } 1334 1335 /** Stops requesting server address from Driver Station. */ 1336 public void stopDSClient() { 1337 NetworkTablesJNI.stopDSClient(m_handle); 1338 } 1339 1340 /** 1341 * Flushes all updated values immediately to the local client/server. This does not flush to the 1342 * network. 1343 */ 1344 public void flushLocal() { 1345 NetworkTablesJNI.flushLocal(m_handle); 1346 } 1347 1348 /** 1349 * Flushes all updated values immediately to the network. Note: This is rate-limited to protect 1350 * the network from flooding. This is primarily useful for synchronizing network updates with 1351 * user code. 1352 */ 1353 public void flush() { 1354 NetworkTablesJNI.flush(m_handle); 1355 } 1356 1357 /** 1358 * Gets information on the currently established network connections. If operating as a client, 1359 * this will return either zero or one values. 1360 * 1361 * @return array of connection information 1362 */ 1363 public ConnectionInfo[] getConnections() { 1364 return NetworkTablesJNI.getConnections(m_handle); 1365 } 1366 1367 /** 1368 * Return whether or not the instance is connected to another node. 1369 * 1370 * @return True if connected. 1371 */ 1372 public boolean isConnected() { 1373 return NetworkTablesJNI.isConnected(m_handle); 1374 } 1375 1376 /** 1377 * Get the time offset between server time and local time. Add this value to local time to get 1378 * the estimated equivalent server time. In server mode, this always returns 0. In client mode, 1379 * this returns the time offset only if the client and server are connected and have exchanged 1380 * synchronization messages. Note the time offset may change over time as it is periodically 1381 * updated; to receive updates as events, add a listener to the "time sync" event. 1382 * 1383 * @return Time offset in microseconds (optional) 1384 */ 1385 public OptionalLong getServerTimeOffset() { 1386 return NetworkTablesJNI.getServerTimeOffset(m_handle); 1387 } 1388 1389 /** 1390 * Starts logging entry changes to a DataLog. 1391 * 1392 * @param log data log object; lifetime must extend until StopEntryDataLog is called or the 1393 * instance is destroyed 1394 * @param prefix only store entries with names that start with this prefix; the prefix is not 1395 * included in the data log entry name 1396 * @param logPrefix prefix to add to data log entry names 1397 * @return Data logger handle 1398 */ 1399 public int startEntryDataLog(DataLog log, String prefix, String logPrefix) { 1400 return NetworkTablesJNI.startEntryDataLog(m_handle, log, prefix, logPrefix); 1401 } 1402 1403 /** 1404 * Stops logging entry changes to a DataLog. 1405 * 1406 * @param logger data logger handle 1407 */ 1408 public static void stopEntryDataLog(int logger) { 1409 NetworkTablesJNI.stopEntryDataLog(logger); 1410 } 1411 1412 /** 1413 * Starts logging connection changes to a DataLog. 1414 * 1415 * @param log data log object; lifetime must extend until StopConnectionDataLog is called or the 1416 * instance is destroyed 1417 * @param name data log entry name 1418 * @return Data logger handle 1419 */ 1420 public int startConnectionDataLog(DataLog log, String name) { 1421 return NetworkTablesJNI.startConnectionDataLog(m_handle, log, name); 1422 } 1423 1424 /** 1425 * Stops logging connection changes to a DataLog. 1426 * 1427 * @param logger data logger handle 1428 */ 1429 public static void stopConnectionDataLog(int logger) { 1430 NetworkTablesJNI.stopConnectionDataLog(logger); 1431 } 1432 1433 /** 1434 * Add logger callback function. By default, log messages are sent to stderr; this function sends 1435 * log messages with the specified levels to the provided callback function instead. The callback 1436 * function will only be called for log messages with level greater than or equal to minLevel and 1437 * less than or equal to maxLevel; messages outside this range will be silently ignored. 1438 * 1439 * @param minLevel minimum log level 1440 * @param maxLevel maximum log level 1441 * @param func callback function 1442 * @return Listener handle 1443 */ 1444 public int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> func) { 1445 return m_listeners.addLogger(minLevel, maxLevel, func); 1446 } 1447 1448 /** 1449 * Returns whether there is a data schema already registered with the given name that this 1450 * instance has published. This does NOT perform a check as to whether the schema has already 1451 * been published by another node on the network. 1452 * 1453 * @param name Name (the string passed as the data type for topics using this schema) 1454 * @return True if schema already registered 1455 */ 1456 public boolean hasSchema(String name) { 1457 return m_schemas.containsKey("/.schema/" + name); 1458 } 1459 1460 /** 1461 * Registers a data schema. Data schemas provide information for how a certain data type string 1462 * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. 1463 * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas 1464 * are published just like normal topics, with the name being generated from the provided name: 1465 * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. 1466 * 1467 * @param name Name (the string passed as the data type for topics using this schema) 1468 * @param type Type of schema (e.g. "protobuf", "struct", etc) 1469 * @param schema Schema data 1470 */ 1471 public void addSchema(String name, String type, byte[] schema) { 1472 m_schemas.computeIfAbsent("/.schema/" + name, k -> { 1473 RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}"); 1474 pub.setDefault(schema); 1475 return pub; 1476 }); 1477 } 1478 1479 /** 1480 * Registers a data schema. Data schemas provide information for how a certain data type string 1481 * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. 1482 * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas 1483 * are published just like normal topics, with the name being generated from the provided name: 1484 * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. 1485 * 1486 * @param name Name (the string passed as the data type for topics using this schema) 1487 * @param type Type of schema (e.g. "protobuf", "struct", etc) 1488 * @param schema Schema data 1489 */ 1490 public void addSchema(String name, String type, String schema) { 1491 m_schemas.computeIfAbsent("/.schema/" + name, k -> { 1492 RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}"); 1493 pub.setDefault(StandardCharsets.UTF_8.encode(schema)); 1494 return pub; 1495 }); 1496 } 1497 1498 /** 1499 * Registers a protobuf schema. Duplicate calls to this function with the same name are silently 1500 * ignored. 1501 * 1502 * @param proto protobuf serialization object 1503 */ 1504 public void addSchema(Protobuf<?, ?> proto) { 1505 proto.forEachDescriptor( 1506 this::hasSchema, 1507 (typeString, schema) -> addSchema(typeString, "proto:FileDescriptorProto", schema)); 1508 } 1509 1510 /** 1511 * Registers a struct schema. Duplicate calls to this function with the same name are silently 1512 * ignored. 1513 * 1514 * @param struct struct serialization object 1515 */ 1516 public void addSchema(Struct<?> struct) { 1517 addSchemaImpl(struct, new HashSet<>()); 1518 } 1519 1520 @Override 1521 public boolean equals(Object other) { 1522 return other == this || other instanceof NetworkTableInstance inst && m_handle == inst.m_handle; 1523 } 1524 1525 @Override 1526 public int hashCode() { 1527 return m_handle; 1528 } 1529 1530 private void addSchemaImpl(Struct<?> struct, Set<String> seen) { 1531 String typeString = struct.getTypeString(); 1532 if (hasSchema(typeString)) { 1533 return; 1534 } 1535 if (!seen.add(typeString)) { 1536 throw new UnsupportedOperationException(typeString + ": circular reference with " + seen); 1537 } 1538 addSchema(typeString, "structschema", struct.getSchema()); 1539 for (Struct<?> inner : struct.getNested()) { 1540 addSchemaImpl(inner, seen); 1541 } 1542 seen.remove(typeString); 1543 } 1544 1545 private boolean m_owned; 1546 private int m_handle; 1547 private final ConcurrentMap<String, RawPublisher> m_schemas = new ConcurrentHashMap<>(); 1548}