001package edu.wpi.first.wpilibj.networktables;
002
003import edu.wpi.first.wpilibj.tables.*;
004import edu.wpi.first.wpilibj.networktables2.type.*;
005import java.io.*;
006import java.nio.ByteBuffer;
007import java.util.*;
008
009/**
010 * A network table that knows its subtable path.
011 */
012public class NetworkTable implements ITable, IRemote {
013  /**
014   * The path separator for sub-tables and keys
015   *
016   */
017  public static final char PATH_SEPARATOR = '/';
018  /**
019   * The default port that network tables operates on
020   */
021  public static final int DEFAULT_PORT = 1735;
022
023  private static boolean client = false;
024  private static boolean enableDS = true;
025  private static boolean running = false;
026  private static int port = DEFAULT_PORT;
027  private static String persistentFilename = "networktables.ini";
028
029  private synchronized static void checkInit() {
030    if (running)
031      throw new IllegalStateException(
032          "Network tables has already been initialized");
033  }
034
035  /**
036   * initializes network tables
037   */
038  public synchronized static void initialize() {
039    if (running)
040      shutdown();
041    if (client) {
042      NetworkTablesJNI.startClient();
043      if (enableDS)
044        NetworkTablesJNI.startDSClient(port);
045    } else
046      NetworkTablesJNI.startServer(persistentFilename, "", port);
047    running = true;
048  }
049
050  /**
051   * shuts down network tables
052   */
053  public synchronized static void shutdown() {
054    if (!running)
055      return;
056    if (client) {
057      NetworkTablesJNI.stopDSClient();
058      NetworkTablesJNI.stopClient();
059    } else
060      NetworkTablesJNI.stopServer();
061    running = false;
062  }
063
064  /**
065   * set that network tables should be a server
066   * This must be called before initialize or getTable
067   */
068  public synchronized static void setServerMode() {
069    if (!client)
070      return;
071    checkInit();
072    client = false;
073  }
074
075  /**
076   * set that network tables should be a client
077   * This must be called before initialize or getTable
078   */
079  public synchronized static void setClientMode() {
080    if (client)
081      return;
082    checkInit();
083    client = true;
084  }
085
086  /**
087   * set the team the robot is configured for (this will set the mdns address that
088   * network tables will connect to in client mode)
089   * This must be called before initialize or getTable
090   * @param team the team number
091   */
092  public synchronized static void setTeam(int team) {
093    String[] addresses = new String[4];
094    addresses[0] = "10." + (int)(team / 100) + "." + (int)(team % 100) + ".2";
095    addresses[1] = "172.22.11.2";
096    addresses[2] = "roboRIO-" + team + "-FRC.local";
097    addresses[3] = "roboRIO-" + team + "-FRC.lan";
098    setIPAddress(addresses);
099  }
100
101  /**
102   * @param address the adress that network tables will connect to in client
103   * mode
104   */
105  public synchronized static void setIPAddress(final String address) {
106    String[] addresses = new String[1];
107    addresses[0] = address;
108    setIPAddress(addresses);
109  }
110
111  /**
112   * @param addresses the adresses that network tables will connect to in
113   * client mode (in round robin order)
114   */
115  public synchronized static void setIPAddress(final String[] addresses) {
116    int[] ports = new int[addresses.length];
117    for (int i=0; i<addresses.length; i++)
118      ports[i] = port;
119    NetworkTablesJNI.setServer(addresses, ports);
120
121    // Stop the DS client if we're explicitly connecting to localhost
122    if (addresses.length > 0 &&
123        (addresses[0].equals("localhost") || addresses[0].equals("127.0.0.1")))
124      NetworkTablesJNI.stopDSClient();
125    else if (enableDS)
126      NetworkTablesJNI.startDSClient(port);
127  }
128
129  /**
130   * @param aport the port number that network tables will connect to in client
131   * mode or listen to in server mode
132   */
133  public synchronized static void setPort(int aport) {
134    if (port == aport)
135      return;
136    checkInit();
137    port = aport;
138  }
139
140  /**
141   * @param enabled whether to enable the connection to the local DS to get
142   * the robot IP address (defaults to enabled)
143   */
144  public synchronized static void setDSClientEnabled(boolean enabled) {
145    enableDS = enabled;
146    if (enableDS)
147      NetworkTablesJNI.startDSClient(port);
148    else
149      NetworkTablesJNI.stopDSClient();
150  }
151
152  /**
153   * Sets the persistent filename.
154   * @param filename the filename that the network tables server uses for
155   * automatic loading and saving of persistent values
156   */
157  public synchronized static void setPersistentFilename(final String filename) {
158    if (persistentFilename.equals(filename))
159      return;
160    checkInit();
161    persistentFilename = filename;
162  }
163
164  /**
165   * Sets the network identity.
166   * This is provided in the connection info on the remote end.
167   * @param name identity
168   */
169  public static void setNetworkIdentity(String name) {
170    NetworkTablesJNI.setNetworkIdentity(name);
171  }
172
173  public static boolean[] toNative(Boolean[] arr) {
174    boolean[] out = new boolean[arr.length];
175    for (int i = 0; i < arr.length; i++)
176      out[i] = arr[i];
177    return out;
178  }
179
180  public static double[] toNative(Number[] arr) {
181    double[] out = new double[arr.length];
182    for (int i = 0; i < arr.length; i++)
183      out[i] = arr[i].doubleValue();
184    return out;
185  }
186
187  public static Boolean[] fromNative(boolean[] arr) {
188    Boolean[] out = new Boolean[arr.length];
189    for (int i = 0; i < arr.length; i++)
190      out[i] = arr[i];
191    return out;
192  }
193
194  public static Double[] fromNative(double[] arr) {
195    Double[] out = new Double[arr.length];
196    for (int i = 0; i < arr.length; i++)
197      out[i] = arr[i];
198    return out;
199  }
200
201  /**
202   * Gets the table with the specified key. If the table does not exist, a new
203   *table will be created.<br>
204   * This will automatically initialize network tables if it has not been
205   *already
206   *
207   * @param key
208   *            the key name
209   * @return the network table requested
210   */
211  public synchronized static NetworkTable getTable(String key) {
212    if (!running)
213      initialize();
214    if (key.isEmpty() || key.charAt(0) == PATH_SEPARATOR)
215      return new NetworkTable(key);
216    return new NetworkTable(PATH_SEPARATOR + key);
217  }
218
219  private final String path;
220  private final String pathWithSep;
221
222  NetworkTable(String path) {
223    this.path = path;
224    this.pathWithSep = path + PATH_SEPARATOR;
225  }
226  public String toString() { return "NetworkTable: " + path; }
227
228  public static ConnectionInfo[] connections() {
229    return NetworkTablesJNI.getConnections();
230  }
231
232  public boolean isConnected() {
233    ConnectionInfo[] conns = NetworkTablesJNI.getConnections();
234    return conns.length > 0;
235  }
236
237  public boolean isServer() {
238    return !client;
239  }
240
241  private static class ListenerBase {
242    public int uid;
243  }
244
245  private static class ConnectionListenerAdapter extends ListenerBase implements NetworkTablesJNI.ConnectionListenerFunction {
246    private final IRemote targetSource;
247    private final IRemoteConnectionListener targetListener;
248
249    public ConnectionListenerAdapter(IRemote targetSource, IRemoteConnectionListener targetListener) {
250      this.targetSource = targetSource;
251      this.targetListener = targetListener;
252    }
253
254    public void apply(int uid, boolean connected, ConnectionInfo conn) {
255      if (connected)
256        targetListener.connectedEx(targetSource, conn);
257      else
258        targetListener.disconnectedEx(targetSource, conn);
259    }
260  }
261  
262  private static IRemote staticRemote = new IRemote() {
263    public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) {
264      NetworkTable.addGlobalConnectionListener(listener, immediateNotify);
265    }
266    public void removeConnectionListener(IRemoteConnectionListener listener) {
267      NetworkTable.removeGlobalConnectionListener(listener);
268    }
269    public boolean isConnected() {
270      ConnectionInfo[] conns = NetworkTablesJNI.getConnections();
271      return conns.length > 0;
272    }
273    public boolean isServer() {
274      return !client;
275    }
276  };
277  
278  private static final Hashtable<IRemoteConnectionListener,ConnectionListenerAdapter> globalConnectionListenerMap = new Hashtable<IRemoteConnectionListener,ConnectionListenerAdapter>();
279  public static synchronized void addGlobalConnectionListener(IRemoteConnectionListener listener,
280                                                              boolean immediateNotify) {
281    ConnectionListenerAdapter adapter = globalConnectionListenerMap.get(listener);
282    if (adapter != null)
283      throw new IllegalStateException("Cannot add the same listener twice");
284    adapter = new ConnectionListenerAdapter(staticRemote, listener);
285    adapter.uid = NetworkTablesJNI.addConnectionListener(adapter, immediateNotify);
286    globalConnectionListenerMap.put(listener, adapter);
287  }
288
289  public static synchronized void removeGlobalConnectionListener(IRemoteConnectionListener listener) {
290    ConnectionListenerAdapter adapter = globalConnectionListenerMap.get(listener);
291    if (adapter != null) {
292      NetworkTablesJNI.removeConnectionListener(adapter.uid);
293      globalConnectionListenerMap.remove(listener);
294    }
295  }
296
297  private final Hashtable<IRemoteConnectionListener,ConnectionListenerAdapter> connectionListenerMap = new Hashtable<IRemoteConnectionListener,ConnectionListenerAdapter>();
298  public synchronized void addConnectionListener(IRemoteConnectionListener listener,
299                                                 boolean immediateNotify) {
300    ConnectionListenerAdapter adapter = connectionListenerMap.get(listener);
301    if (adapter != null)
302      throw new IllegalStateException("Cannot add the same listener twice");
303    adapter = new ConnectionListenerAdapter(this, listener);
304    adapter.uid = NetworkTablesJNI.addConnectionListener(adapter, immediateNotify);
305    connectionListenerMap.put(listener, adapter);
306  }
307
308  public synchronized void removeConnectionListener(IRemoteConnectionListener listener) {
309    ConnectionListenerAdapter adapter = connectionListenerMap.get(listener);
310    if (adapter != null) {
311      NetworkTablesJNI.removeConnectionListener(adapter.uid);
312      connectionListenerMap.remove(listener);
313    }
314  }
315
316  /**
317   * {@inheritDoc}
318   */
319  @Override
320  public void addTableListener(ITableListener listener) {
321    addTableListenerEx(listener, NOTIFY_NEW | NOTIFY_UPDATE);
322  }
323
324  /**
325   * {@inheritDoc}
326   */
327  @Override
328  public void addTableListener(ITableListener listener,
329                               boolean immediateNotify) {
330    int flags = NOTIFY_NEW | NOTIFY_UPDATE;
331    if (immediateNotify)
332      flags |= NOTIFY_IMMEDIATE;
333    addTableListenerEx(listener, flags);
334  }
335
336  private class TableListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction {
337    private final int prefixLen;
338    private final ITable targetSource;
339    private final ITableListener targetListener;
340
341    public TableListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) {
342      this.prefixLen = prefixLen;
343      this.targetSource = targetSource;
344      this.targetListener = targetListener;
345    }
346
347    public void apply(int uid, String key, Object value, int flags) {
348      String relativeKey = key.substring(prefixLen);
349      if (relativeKey.indexOf(PATH_SEPARATOR) != -1)
350        return;
351      targetListener.valueChangedEx(targetSource, relativeKey, value, flags);
352    }
353  }
354
355  private final Hashtable<ITableListener,List<ListenerBase>> listenerMap = new Hashtable<ITableListener,List<ListenerBase>>();
356  public synchronized void addTableListenerEx(ITableListener listener,
357                                              int flags) {
358    List<ListenerBase> adapters = listenerMap.get(listener);
359    if (adapters == null) {
360      adapters = new ArrayList<ListenerBase>();
361      listenerMap.put(listener, adapters);
362    }
363    TableListenerAdapter adapter =
364        new TableListenerAdapter(path.length() + 1, this, listener);
365    adapter.uid = NetworkTablesJNI.addEntryListener(pathWithSep, adapter, flags);
366    adapters.add(adapter);
367  }
368
369  /**
370   * {@inheritDoc}
371   */
372  @Override
373  public void addTableListener(String key, ITableListener listener,
374                               boolean immediateNotify) {
375    int flags = NOTIFY_NEW | NOTIFY_UPDATE;
376    if (immediateNotify)
377      flags |= NOTIFY_IMMEDIATE;
378    addTableListenerEx(key, listener, flags);
379  }
380
381  private class KeyListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction {
382    private final String relativeKey;
383    private final String fullKey;
384    private final ITable targetSource;
385    private final ITableListener targetListener;
386
387    public KeyListenerAdapter(String relativeKey, String fullKey, ITable targetSource, ITableListener targetListener) {
388      this.relativeKey = relativeKey;
389      this.fullKey = fullKey;
390      this.targetSource = targetSource;
391      this.targetListener = targetListener;
392    }
393
394    public void apply(int uid, String key, Object value, int flags) {
395      if (!key.equals(fullKey))
396        return;
397      targetListener.valueChangedEx(targetSource, relativeKey, value, flags);
398    }
399  }
400
401  /**
402   * {@inheritDoc}
403   */
404  @Override
405  public synchronized void addTableListenerEx(String key,
406                                              ITableListener listener,
407                                              int flags) {
408    List<ListenerBase> adapters = listenerMap.get(listener);
409    if (adapters == null) {
410      adapters = new ArrayList<ListenerBase>();
411      listenerMap.put(listener, adapters);
412    }
413    String fullKey = pathWithSep + key;
414    KeyListenerAdapter adapter =
415        new KeyListenerAdapter(key, fullKey, this, listener);
416    adapter.uid = NetworkTablesJNI.addEntryListener(fullKey, adapter, flags);
417    adapters.add(adapter);
418  }
419
420  /**
421   * {@inheritDoc}
422   */
423  @Override
424  public void addSubTableListener(final ITableListener listener) {
425    addSubTableListener(listener, false);
426  }
427
428  private class SubListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction {
429    private final int prefixLen;
430    private final ITable targetSource;
431    private final ITableListener targetListener;
432    private final Set<String> notifiedTables = new HashSet<String>();
433
434    public SubListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) {
435      this.prefixLen = prefixLen;
436      this.targetSource = targetSource;
437      this.targetListener = targetListener;
438    }
439
440    public void apply(int uid, String key, Object value, int flags) {
441      String relativeKey = key.substring(prefixLen);
442      int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
443      if (endSubTable == -1)
444        return;
445      String subTableKey = relativeKey.substring(0, endSubTable);
446      if (notifiedTables.contains(subTableKey))
447        return;
448      notifiedTables.add(subTableKey);
449      targetListener.valueChangedEx(targetSource, subTableKey, targetSource.getSubTable(subTableKey), flags);
450    }
451  }
452
453  /**
454   * {@inheritDoc}
455   */
456  @Override
457  public synchronized void addSubTableListener(final ITableListener listener,
458                                               boolean localNotify) {
459    List<ListenerBase> adapters = listenerMap.get(listener);
460    if (adapters == null) {
461      adapters = new ArrayList<ListenerBase>();
462      listenerMap.put(listener, adapters);
463    }
464    SubListenerAdapter adapter =
465        new SubListenerAdapter(path.length() + 1, this, listener);
466    int flags = NOTIFY_NEW | NOTIFY_IMMEDIATE;
467    if (localNotify)
468      flags |= NOTIFY_LOCAL;
469    adapter.uid = NetworkTablesJNI.addEntryListener(pathWithSep, adapter, flags);
470    adapters.add(adapter);
471  }
472
473  /**
474   * {@inheritDoc}
475   */
476  @Override
477  public synchronized void removeTableListener(ITableListener listener) {
478    List<ListenerBase> adapters = listenerMap.get(listener);
479    if (adapters != null) {
480      for (int i = 0; i < adapters.size(); ++i)
481        NetworkTablesJNI.removeEntryListener(adapters.get(i).uid);
482      adapters.clear();
483    }
484  }
485
486  /**
487   * {@inheritDoc}
488   */
489  @Override
490  public ITable getSubTable(String key) {
491    return new NetworkTable(pathWithSep + key);
492  }
493
494  /**
495   * {@inheritDoc}
496   */
497  @Override
498  public boolean containsKey(String key) {
499    return NetworkTablesJNI.containsKey(pathWithSep + key);
500  }
501
502  public boolean containsSubTable(String key) {
503    EntryInfo[] entries = NetworkTablesJNI.getEntries(pathWithSep + key + PATH_SEPARATOR, 0);
504    return entries.length != 0;
505  }
506
507  /**
508   * @param types bitmask of types; 0 is treated as a "don't care".
509   * @return keys currently in the table
510   */
511  public Set<String> getKeys(int types) {
512    Set<String> keys = new HashSet<String>();
513    int prefixLen = path.length() + 1;
514    for (EntryInfo entry : NetworkTablesJNI.getEntries(pathWithSep, types)) {
515      String relativeKey = entry.name.substring(prefixLen);
516      if (relativeKey.indexOf(PATH_SEPARATOR) != -1)
517        continue;
518      keys.add(relativeKey);
519    }
520    return keys;
521  }
522
523  /**
524   * {@inheritDoc}
525   */
526  @Override
527  public Set<String> getKeys() {
528    return getKeys(0);
529  }
530
531  /**
532   * {@inheritDoc}
533   */
534  @Override
535  public Set<String> getSubTables() {
536    Set<String> keys = new HashSet<String>();
537    int prefixLen = path.length() + 1;
538    for (EntryInfo entry : NetworkTablesJNI.getEntries(pathWithSep, 0)) {
539      String relativeKey = entry.name.substring(prefixLen);
540      int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
541      if (endSubTable == -1)
542        continue;
543      keys.add(relativeKey.substring(0, endSubTable));
544    }
545    return keys;
546  }
547
548  /**
549   * {@inheritDoc}
550   */
551  @Override
552  public boolean putNumber(String key, double value) {
553    return NetworkTablesJNI.putDouble(pathWithSep + key, value);
554  }
555  
556  /**
557   * {@inheritDoc}
558   */
559  public boolean setDefaultNumber(String key, double defaultValue) {
560    return NetworkTablesJNI.setDefaultDouble(pathWithSep + key,
561                                             defaultValue);
562  }
563
564  /**
565   * {@inheritDoc}
566   * @deprecated This exception-raising method has been replaced by the
567   * default-taking method {@link #getNumber(String, double)}.
568   */
569  @Override
570  @Deprecated
571  public double getNumber(String key) throws TableKeyNotDefinedException {
572    return NetworkTablesJNI.getDouble(pathWithSep + key);
573  }
574
575  /**
576   * {@inheritDoc}
577   */
578  @Override
579  public double getNumber(String key, double defaultValue) {
580    return NetworkTablesJNI.getDouble(pathWithSep + key, defaultValue);
581  }
582
583  /**
584   * {@inheritDoc}
585   */
586  @Override
587  public boolean putString(String key, String value) {
588    return NetworkTablesJNI.putString(pathWithSep + key, value);
589  }
590  
591  /**
592   * {@inheritDoc}
593   */
594  public boolean setDefaultString(String key, String defaultValue) {
595    return NetworkTablesJNI.setDefaultString(pathWithSep + key,
596                                             defaultValue);
597  }
598
599  /**
600   * {@inheritDoc}
601   * @deprecated This exception-raising method has been replaced by the
602   * default-taking method {@link #getString(String, String)}.
603   */
604  @Override
605  @Deprecated
606  public String getString(String key) throws TableKeyNotDefinedException {
607    return NetworkTablesJNI.getString(pathWithSep + key);
608  }
609
610  /**
611   * {@inheritDoc}
612   */
613  @Override
614  public String getString(String key, String defaultValue) {
615    return NetworkTablesJNI.getString(pathWithSep + key, defaultValue);
616  }
617
618  /**
619   * {@inheritDoc}
620   */
621  @Override
622  public boolean putBoolean(String key, boolean value) {
623    return NetworkTablesJNI.putBoolean(pathWithSep + key, value);
624  }
625  
626  /**
627   * {@inheritDoc}
628   */
629  public boolean setDefaultBoolean(String key, boolean defaultValue) {
630    return NetworkTablesJNI.setDefaultBoolean(pathWithSep + key,
631                                              defaultValue);
632  }
633
634  /**
635   * {@inheritDoc}
636   * @deprecated This exception-raising method has been replaced by the
637   * default-taking method {@link #getBoolean(String, boolean)}.
638   */
639  @Override
640  @Deprecated
641  public boolean getBoolean(String key) throws TableKeyNotDefinedException {
642    return NetworkTablesJNI.getBoolean(pathWithSep + key);
643  }
644
645  /**
646   * {@inheritDoc}
647   */
648  @Override
649  public boolean getBoolean(String key, boolean defaultValue) {
650    return NetworkTablesJNI.getBoolean(pathWithSep + key, defaultValue);
651  }
652
653  /**
654   * {@inheritDoc}
655   */
656  @Override
657  public boolean putBooleanArray(String key, boolean[] value) {
658    return NetworkTablesJNI.putBooleanArray(pathWithSep + key, value);
659  }
660
661  /**
662   * {@inheritDoc}
663   */
664  @Override
665  public boolean putBooleanArray(String key, Boolean[] value) {
666    return putBooleanArray(key, toNative(value));
667  }
668  
669  /**
670   * {@inheritDoc}
671   */
672  public boolean setDefaultBooleanArray(String key, boolean[] defaultValue) {
673    return NetworkTablesJNI.setDefaultBooleanArray(pathWithSep + key,
674                                                   defaultValue);
675  }
676  
677  /**
678   * {@inheritDoc}
679   */
680  public boolean setDefaultBooleanArray(String key, Boolean[] defaultValue) {
681    return NetworkTablesJNI.setDefaultBooleanArray(pathWithSep + key,
682                                                   toNative(defaultValue));
683  }
684
685  /**
686   * {@inheritDoc}
687   * @deprecated This exception-raising method has been replaced by the
688   * default-taking method {@link #getBooleanArray(String, boolean[])}.
689   */
690  @Override
691  @Deprecated
692  public boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException {
693    return NetworkTablesJNI.getBooleanArray(pathWithSep + key);
694  }
695
696  /**
697   * {@inheritDoc}
698   */
699  @Override
700  public boolean[] getBooleanArray(String key, boolean[] defaultValue) {
701    return NetworkTablesJNI.getBooleanArray(pathWithSep + key, defaultValue);
702  }
703
704  /**
705   * {@inheritDoc}
706   */
707  @Override
708  public Boolean[] getBooleanArray(String key, Boolean[] defaultValue) {
709    try {
710      return fromNative(getBooleanArray(key));
711    } catch (TableKeyNotDefinedException e) {
712      return defaultValue;
713    }
714  }
715
716  /**
717   * {@inheritDoc}
718   */
719  @Override
720  public boolean putNumberArray(String key, double[] value) {
721    return NetworkTablesJNI.putDoubleArray(pathWithSep + key, value);
722  }
723
724  /**
725   * {@inheritDoc}
726   */
727  @Override
728  public boolean putNumberArray(String key, Double[] value) {
729    return putNumberArray(key, toNative(value));
730  }
731  
732  /**
733   * {@inheritDoc}
734   */
735  public boolean setDefaultNumberArray(String key, double[] defaultValue) {
736    return NetworkTablesJNI.setDefaultDoubleArray(pathWithSep + key,
737                                                  defaultValue);
738  }
739  
740  /**
741   * {@inheritDoc}
742   */
743  public boolean setDefaultNumberArray(String key, Double[] defaultValue) {
744    return NetworkTablesJNI.setDefaultDoubleArray(pathWithSep + key,
745                                                  toNative(defaultValue));
746  }
747
748  /**
749   * {@inheritDoc}
750   * @deprecated This exception-raising method has been replaced by the
751   * default-taking method {@link #getNumberArray(String, double[])}.
752   */
753  @Override
754  @Deprecated
755  public double[] getNumberArray(String key) throws TableKeyNotDefinedException {
756    return NetworkTablesJNI.getDoubleArray(pathWithSep + key);
757  }
758
759  /**
760   * {@inheritDoc}
761   */
762  @Override
763  public double[] getNumberArray(String key, double[] defaultValue) {
764    return NetworkTablesJNI.getDoubleArray(pathWithSep + key, defaultValue);
765  }
766
767  /**
768   * {@inheritDoc}
769   */
770  @Override
771  public Double[] getNumberArray(String key, Double[] defaultValue) {
772    try {
773      return fromNative(getNumberArray(key));
774    } catch (TableKeyNotDefinedException e) {
775      return defaultValue;
776    }
777  }
778
779  /**
780   * {@inheritDoc}
781   */
782  @Override
783  public boolean putStringArray(String key, String[] value) {
784    return NetworkTablesJNI.putStringArray(pathWithSep + key, value);
785  }
786  
787  /**
788   * {@inheritDoc}
789   */
790  public boolean setDefaultStringArray(String key, String[] defaultValue) {
791    return NetworkTablesJNI.setDefaultStringArray(pathWithSep + key,
792                                                  defaultValue);
793  }
794  
795  /**
796   * {@inheritDoc}
797   * @deprecated This exception-raising method has been replaced by the
798   * default-taking method {@link #getStringArray(String, String[])}.
799   */
800  @Override
801  @Deprecated
802  public String[] getStringArray(String key) throws TableKeyNotDefinedException {
803    return NetworkTablesJNI.getStringArray(pathWithSep + key);
804  }
805
806  /**
807   * {@inheritDoc}
808   */
809  @Override
810  public String[] getStringArray(String key, String[] defaultValue) {
811    return NetworkTablesJNI.getStringArray(pathWithSep + key, defaultValue);
812  }
813
814  /**
815   * {@inheritDoc}
816   */
817  @Override
818  public boolean putRaw(String key, byte[] value) {
819    return NetworkTablesJNI.putRaw(pathWithSep + key, value);
820  }
821  
822  /**
823   * {@inheritDoc}
824   */
825  public boolean setDefaultRaw(String key, byte[] defaultValue) {
826    return NetworkTablesJNI.setDefaultRaw(pathWithSep + key,
827                                          defaultValue);
828  }
829
830  /**
831   * {@inheritDoc}
832   */
833  @Override
834  public boolean putRaw(String key, ByteBuffer value, int len) {
835    if (!value.isDirect())
836      throw new IllegalArgumentException("must be a direct buffer");
837    if (value.capacity() < len)
838      throw new IllegalArgumentException("buffer is too small, must be at least " + len);
839    return NetworkTablesJNI.putRaw(pathWithSep + key, value, len);
840  }
841
842  /**
843   * {@inheritDoc}
844   * @deprecated This exception-raising method has been replaced by the
845   * default-taking method {@link #getRaw(String, byte[])}.
846   */
847  @Override
848  @Deprecated
849  public byte[] getRaw(String key) throws TableKeyNotDefinedException {
850    return NetworkTablesJNI.getRaw(pathWithSep + key);
851  }
852
853  /**
854   * {@inheritDoc}
855   */
856  @Override
857  public byte[] getRaw(String key, byte[] defaultValue) {
858    return NetworkTablesJNI.getRaw(pathWithSep + key, defaultValue);
859  }
860
861  /**
862   * {@inheritDoc}
863   */
864  @Override
865  public boolean putValue(String key, Object value) throws IllegalArgumentException {
866    if (value instanceof Boolean)
867      return NetworkTablesJNI.putBoolean(pathWithSep + key, ((Boolean)value).booleanValue());
868    else if (value instanceof Number)
869      return NetworkTablesJNI.putDouble(pathWithSep + key, ((Number)value).doubleValue());
870    else if (value instanceof String)
871      return NetworkTablesJNI.putString(pathWithSep + key, (String)value);
872    else if (value instanceof byte[])
873      return NetworkTablesJNI.putRaw(pathWithSep + key, (byte[])value);
874    else if (value instanceof boolean[])
875      return NetworkTablesJNI.putBooleanArray(pathWithSep + key, (boolean[])value);
876    else if (value instanceof double[])
877      return NetworkTablesJNI.putDoubleArray(pathWithSep + key, (double[])value);
878    else if (value instanceof Boolean[])
879      return NetworkTablesJNI.putBooleanArray(pathWithSep + key, toNative((Boolean[])value));
880    else if (value instanceof Number[])
881      return NetworkTablesJNI.putDoubleArray(pathWithSep + key, toNative((Number[])value));
882    else if (value instanceof String[])
883      return NetworkTablesJNI.putStringArray(pathWithSep + key, (String[])value);
884    else if (value instanceof BooleanArray)
885      return NetworkTablesJNI.putBooleanArray(pathWithSep + key, toNative((Boolean[])((ArrayData)value).getDataArray()));
886    else if (value instanceof NumberArray)
887      return NetworkTablesJNI.putDoubleArray(pathWithSep + key, toNative((Double[])((ArrayData)value).getDataArray()));
888    else if (value instanceof StringArray)
889      return NetworkTablesJNI.putStringArray(pathWithSep + key, (String[])((ArrayData)value).getDataArray());
890    else
891      throw new IllegalArgumentException("Value of type " + value.getClass().getName() + " cannot be put into a table");
892  }
893
894  /**
895   * {@inheritDoc}
896   * @deprecated Use get*Array functions instead.
897   */
898  @Override
899  @Deprecated
900  public void retrieveValue(String key, Object externalData) throws TableKeyNotDefinedException {
901    Object value = getValue(key);
902    if (value instanceof boolean[] && externalData instanceof BooleanArray)
903      ((ArrayData)externalData).setDataArray(fromNative((boolean[])value));
904    else if (value instanceof double[] && externalData instanceof NumberArray)
905      ((ArrayData)externalData).setDataArray(fromNative((double[])value));
906    else if (value instanceof String[] && externalData instanceof StringArray)
907      ((ArrayData)externalData).setDataArray((String[])value);
908    else
909      throw new TableKeyNotDefinedException(key);
910  }
911
912  /**
913   * {@inheritDoc}
914   * @deprecated This exception-raising method has been replaced by the
915   * default-taking method {@link #getValue(String, Object)}.
916   */
917  @Override
918  @Deprecated
919  public Object getValue(String key) throws TableKeyNotDefinedException {
920    return NetworkTablesJNI.getValue(pathWithSep + key);
921  }
922
923  /**
924   * {@inheritDoc}
925   */
926  @Override
927  public Object getValue(String key, Object defaultValue) {
928    return NetworkTablesJNI.getValue(pathWithSep + key, defaultValue);
929  }
930
931  /** The persistent flag value. */
932  public static final int PERSISTENT = 1;
933
934  /**
935   * {@inheritDoc}
936   */
937  @Override
938  public void setPersistent(String key) {
939    setFlags(key, PERSISTENT);
940  }
941
942  /**
943   * {@inheritDoc}
944   */
945  @Override
946  public void clearPersistent(String key) {
947    clearFlags(key, PERSISTENT);
948  }
949
950  /**
951   * {@inheritDoc}
952   */
953  @Override
954  public boolean isPersistent(String key) {
955    return (getFlags(key) & PERSISTENT) != 0;
956  }
957
958  /**
959   * {@inheritDoc}
960   */
961  @Override
962  public void setFlags(String key, int flags) {
963    NetworkTablesJNI.setEntryFlags(pathWithSep + key, getFlags(key) | flags);
964  }
965
966  /**
967   * {@inheritDoc}
968   */
969  @Override
970  public void clearFlags(String key, int flags) {
971    NetworkTablesJNI.setEntryFlags(pathWithSep + key, getFlags(key) & ~flags);
972  }
973
974  /**
975   * {@inheritDoc}
976   */
977  @Override
978  public int getFlags(String key) {
979    return NetworkTablesJNI.getEntryFlags(pathWithSep + key);
980  }
981
982  /**
983   * {@inheritDoc}
984   */
985  @Override
986  public void delete(String key) {
987    NetworkTablesJNI.deleteEntry(pathWithSep + key);
988  }
989
990  /**
991   * Deletes ALL keys in ALL subtables.  Use with caution!
992   */
993  public static void globalDeleteAll() {
994    NetworkTablesJNI.deleteAllEntries();
995  }
996
997  /**
998   * Flushes all updated values immediately to the network.
999   * Note: This is rate-limited to protect the network from flooding.
1000   * This is primarily useful for synchronizing network updates with
1001   * user code.
1002   */
1003  public static void flush() {
1004    NetworkTablesJNI.flush();
1005  }
1006
1007  /**
1008   * Set the periodic update rate.
1009   *
1010   * @param interval update interval in seconds (range 0.01 to 1.0)
1011   */
1012  public static void setUpdateRate(double interval) {
1013    NetworkTablesJNI.setUpdateRate(interval);
1014  }
1015
1016  /**
1017   * Saves persistent keys to a file.  The server does this automatically.
1018   *
1019   * @param filename file name
1020   * @throws PersistentException if error saving file
1021   */
1022  public static void savePersistent(String filename) throws PersistentException {
1023    NetworkTablesJNI.savePersistent(filename);
1024  }
1025
1026  /**
1027   * Loads persistent keys from a file.  The server does this automatically.
1028   *
1029   * @param filename file name
1030   * @return List of warnings (errors result in an exception instead)
1031   * @throws PersistentException if error reading file
1032   */
1033  public static String[] loadPersistent(String filename) throws PersistentException {
1034    return NetworkTablesJNI.loadPersistent(filename);
1035  }
1036
1037  /*
1038   * Deprecated Methods
1039   */
1040
1041  /**
1042   * {@inheritDoc}
1043   * @deprecated Use {@link #putNumber(String, double)} instead.
1044   */
1045  @Override
1046  @Deprecated
1047  public boolean putInt(String key, int value) {
1048    return putNumber(key, value);
1049  }
1050
1051  /**
1052   * {@inheritDoc}
1053   * @deprecated Use {@link #getNumber(String, double)} instead.
1054   */
1055  @Override
1056  @Deprecated
1057  public int getInt(String key) throws TableKeyNotDefinedException {
1058    return (int)getNumber(key);
1059  }
1060
1061  /**
1062   * {@inheritDoc}
1063   * @deprecated Use {@link #getNumber(String, double)} instead.
1064   */
1065  @Override
1066  @Deprecated
1067  public int getInt(String key, int defaultValue) throws TableKeyNotDefinedException {
1068    try {
1069      return (int)getNumber(key);
1070    } catch (NoSuchElementException ex) {
1071      return defaultValue;
1072    }
1073  }
1074
1075  /**
1076   * {@inheritDoc}
1077   * @deprecated Use {@link #putNumber(String, double)} instead.
1078   */
1079  @Override
1080  @Deprecated
1081  public boolean putDouble(String key, double value) {
1082    return putNumber(key, value);
1083  }
1084
1085  /**
1086   * {@inheritDoc}
1087   * @deprecated Use {@link #getNumber(String, double)} instead.
1088   */
1089  @Override
1090  @Deprecated
1091  public double getDouble(String key) throws TableKeyNotDefinedException {
1092    return getNumber(key);
1093  }
1094
1095  /**
1096   * {@inheritDoc}
1097   * @deprecated Use {@link #getNumber(String, double)} instead.
1098   */
1099  @Override
1100  @Deprecated
1101  public double getDouble(String key, double defaultValue) {
1102    return getNumber(key, defaultValue);
1103  }
1104}