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