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