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