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.wpilibj;
006
007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.hal.FRCNetComm.tResourceType;
010import edu.wpi.first.hal.HAL;
011import edu.wpi.first.networktables.MultiSubscriber;
012import edu.wpi.first.networktables.NetworkTable;
013import edu.wpi.first.networktables.NetworkTableEntry;
014import edu.wpi.first.networktables.NetworkTableEvent;
015import edu.wpi.first.networktables.NetworkTableInstance;
016import edu.wpi.first.networktables.NetworkTableListener;
017import edu.wpi.first.networktables.StringPublisher;
018import edu.wpi.first.networktables.Topic;
019import java.util.Collection;
020import java.util.EnumSet;
021
022/**
023 * The preferences class provides a relatively simple way to save important values to the roboRIO to
024 * access the next time the roboRIO is booted.
025 *
026 * <p>This class loads and saves from a file inside the roboRIO. The user can not access the file
027 * directly, but may modify values at specific fields which will then be automatically saved to the
028 * file by the NetworkTables server.
029 *
030 * <p>This class is thread safe.
031 *
032 * <p>This will also interact with {@link NetworkTable} by creating a table called "Preferences"
033 * with all the key-value pairs.
034 */
035public final class Preferences {
036  /** The Preferences table name. */
037  private static final String TABLE_NAME = "Preferences";
038  /** The network table. */
039  private static NetworkTable m_table;
040
041  private static StringPublisher m_typePublisher;
042  private static MultiSubscriber m_tableSubscriber;
043  private static NetworkTableListener m_listener;
044
045  /** Creates a preference class. */
046  private Preferences() {}
047
048  static {
049    setNetworkTableInstance(NetworkTableInstance.getDefault());
050    HAL.report(tResourceType.kResourceType_Preferences, 0);
051  }
052
053  /**
054   * Set the NetworkTable instance used for entries. For testing purposes; use with caution.
055   *
056   * @param inst NetworkTable instance
057   */
058  public static synchronized void setNetworkTableInstance(NetworkTableInstance inst) {
059    m_table = inst.getTable(TABLE_NAME);
060    if (m_typePublisher != null) {
061      m_typePublisher.close();
062    }
063    m_typePublisher = m_table.getStringTopic(".type").publish();
064    m_typePublisher.set("RobotPreferences");
065
066    // Subscribe to all Preferences; this ensures we get the latest values
067    // ahead of a getter call.
068    if (m_tableSubscriber != null) {
069      m_tableSubscriber.close();
070    }
071    m_tableSubscriber = new MultiSubscriber(inst, new String[] {m_table.getPath() + "/"});
072
073    // Listener to set all Preferences values to persistent
074    // (for backwards compatibility with old dashboards).
075    if (m_listener != null) {
076      m_listener.close();
077    }
078    m_listener =
079        NetworkTableListener.createListener(
080            m_tableSubscriber,
081            EnumSet.of(NetworkTableEvent.Kind.kImmediate, NetworkTableEvent.Kind.kPublish),
082            event -> {
083              if (event.topicInfo != null) {
084                Topic topic = event.topicInfo.getTopic();
085                if (!topic.equals(m_typePublisher.getTopic())) {
086                  event.topicInfo.getTopic().setPersistent(true);
087                }
088              }
089            });
090  }
091
092  /**
093   * Gets the preferences keys.
094   *
095   * @return a collection of the keys
096   */
097  public static Collection<String> getKeys() {
098    return m_table.getKeys();
099  }
100
101  /**
102   * Puts the given string into the preferences table.
103   *
104   * @param key the key
105   * @param value the value
106   * @throws NullPointerException if value is null
107   */
108  public static void setString(String key, String value) {
109    requireNonNullParam(value, "value", "setString");
110
111    NetworkTableEntry entry = m_table.getEntry(key);
112    entry.setString(value);
113    entry.setPersistent();
114  }
115
116  /**
117   * Puts the given string into the preferences table if it doesn't already exist.
118   *
119   * @param key The key
120   * @param value The value
121   */
122  public static void initString(String key, String value) {
123    NetworkTableEntry entry = m_table.getEntry(key);
124    entry.setDefaultString(value);
125    entry.setPersistent();
126  }
127
128  /**
129   * Puts the given int into the preferences table.
130   *
131   * @param key the key
132   * @param value the value
133   */
134  public static void setInt(String key, int value) {
135    NetworkTableEntry entry = m_table.getEntry(key);
136    entry.setDouble(value);
137    entry.setPersistent();
138  }
139
140  /**
141   * Puts the given int into the preferences table if it doesn't already exist.
142   *
143   * @param key The key
144   * @param value The value
145   */
146  public static void initInt(String key, int value) {
147    NetworkTableEntry entry = m_table.getEntry(key);
148    entry.setDefaultDouble(value);
149    entry.setPersistent();
150  }
151
152  /**
153   * Puts the given double into the preferences table.
154   *
155   * @param key the key
156   * @param value the value
157   */
158  public static void setDouble(String key, double value) {
159    NetworkTableEntry entry = m_table.getEntry(key);
160    entry.setDouble(value);
161    entry.setPersistent();
162  }
163
164  /**
165   * Puts the given double into the preferences table if it doesn't already exist.
166   *
167   * @param key The key
168   * @param value The value
169   */
170  public static void initDouble(String key, double value) {
171    NetworkTableEntry entry = m_table.getEntry(key);
172    entry.setDefaultDouble(value);
173    entry.setPersistent();
174  }
175
176  /**
177   * Puts the given float into the preferences table.
178   *
179   * @param key the key
180   * @param value the value
181   */
182  public static void setFloat(String key, float value) {
183    NetworkTableEntry entry = m_table.getEntry(key);
184    entry.setDouble(value);
185    entry.setPersistent();
186  }
187
188  /**
189   * Puts the given float into the preferences table if it doesn't already exist.
190   *
191   * @param key The key
192   * @param value The value
193   */
194  public static void initFloat(String key, float value) {
195    NetworkTableEntry entry = m_table.getEntry(key);
196    entry.setDefaultDouble(value);
197    entry.setPersistent();
198  }
199
200  /**
201   * Puts the given boolean into the preferences table.
202   *
203   * @param key the key
204   * @param value the value
205   */
206  public static void setBoolean(String key, boolean value) {
207    NetworkTableEntry entry = m_table.getEntry(key);
208    entry.setBoolean(value);
209    entry.setPersistent();
210  }
211
212  /**
213   * Puts the given boolean into the preferences table if it doesn't already exist.
214   *
215   * @param key The key
216   * @param value The value
217   */
218  public static void initBoolean(String key, boolean value) {
219    NetworkTableEntry entry = m_table.getEntry(key);
220    entry.setDefaultBoolean(value);
221    entry.setPersistent();
222  }
223
224  /**
225   * Puts the given long into the preferences table.
226   *
227   * @param key the key
228   * @param value the value
229   */
230  public static void setLong(String key, long value) {
231    NetworkTableEntry entry = m_table.getEntry(key);
232    entry.setDouble(value);
233    entry.setPersistent();
234  }
235
236  /**
237   * Puts the given long into the preferences table if it doesn't already exist.
238   *
239   * @param key The key
240   * @param value The value
241   */
242  public static void initLong(String key, long value) {
243    NetworkTableEntry entry = m_table.getEntry(key);
244    entry.setDefaultDouble(value);
245    entry.setPersistent();
246  }
247
248  /**
249   * Returns whether there is a key with the given name.
250   *
251   * @param key the key
252   * @return if there is a value at the given key
253   */
254  public static boolean containsKey(String key) {
255    return m_table.containsKey(key);
256  }
257
258  /**
259   * Remove a preference.
260   *
261   * @param key the key
262   */
263  public static void remove(String key) {
264    NetworkTableEntry entry = m_table.getEntry(key);
265    entry.clearPersistent();
266    entry.unpublish();
267  }
268
269  /** Remove all preferences. */
270  public static void removeAll() {
271    for (String key : m_table.getKeys()) {
272      if (!".type".equals(key)) {
273        remove(key);
274      }
275    }
276  }
277
278  /**
279   * Returns the string at the given key. If this table does not have a value for that position,
280   * then the given backup value will be returned.
281   *
282   * @param key the key
283   * @param backup the value to return if none exists in the table
284   * @return either the value in the table, or the backup
285   */
286  public static String getString(String key, String backup) {
287    return m_table.getEntry(key).getString(backup);
288  }
289
290  /**
291   * Returns the int at the given key. If this table does not have a value for that position, then
292   * the given backup value will be returned.
293   *
294   * @param key the key
295   * @param backup the value to return if none exists in the table
296   * @return either the value in the table, or the backup
297   */
298  public static int getInt(String key, int backup) {
299    return (int) m_table.getEntry(key).getDouble(backup);
300  }
301
302  /**
303   * Returns the double at the given key. If this table does not have a value for that position,
304   * then the given backup value will be returned.
305   *
306   * @param key the key
307   * @param backup the value to return if none exists in the table
308   * @return either the value in the table, or the backup
309   */
310  public static double getDouble(String key, double backup) {
311    return m_table.getEntry(key).getDouble(backup);
312  }
313
314  /**
315   * Returns the boolean at the given key. If this table does not have a value for that position,
316   * then the given backup value will be returned.
317   *
318   * @param key the key
319   * @param backup the value to return if none exists in the table
320   * @return either the value in the table, or the backup
321   */
322  public static boolean getBoolean(String key, boolean backup) {
323    return m_table.getEntry(key).getBoolean(backup);
324  }
325
326  /**
327   * Returns the float at the given key. If this table does not have a value for that position, then
328   * the given backup value will be returned.
329   *
330   * @param key the key
331   * @param backup the value to return if none exists in the table
332   * @return either the value in the table, or the backup
333   */
334  public static float getFloat(String key, float backup) {
335    return (float) m_table.getEntry(key).getDouble(backup);
336  }
337
338  /**
339   * Returns the long at the given key. If this table does not have a value for that position, then
340   * the given backup value will be returned.
341   *
342   * @param key the key
343   * @param backup the value to return if none exists in the table
344   * @return either the value in the table, or the backup
345   */
346  public static long getLong(String key, long backup) {
347    return (long) m_table.getEntry(key).getDouble(backup);
348  }
349}