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