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 preferences keys.
102   *
103   * @return a collection of the keys
104   */
105  public static Collection<String> getKeys() {
106    return m_table.getKeys();
107  }
108
109  /**
110   * Puts the given string into the preferences table.
111   *
112   * @param key the key
113   * @param value the value
114   * @throws NullPointerException if value is null
115   */
116  public static void setString(String key, String value) {
117    requireNonNullParam(value, "value", "setString");
118
119    NetworkTableEntry entry = m_table.getEntry(key);
120    entry.setString(value);
121    entry.setPersistent();
122  }
123
124  /**
125   * Puts the given string into the preferences table if it doesn't already exist.
126   *
127   * @param key The key
128   * @param value The value
129   */
130  public static void initString(String key, String value) {
131    NetworkTableEntry entry = m_table.getEntry(key);
132    entry.setDefaultString(value);
133    entry.setPersistent();
134  }
135
136  /**
137   * Puts the given int into the preferences table.
138   *
139   * @param key the key
140   * @param value the value
141   */
142  public static void setInt(String key, int value) {
143    NetworkTableEntry entry = m_table.getEntry(key);
144    entry.setDouble(value);
145    entry.setPersistent();
146  }
147
148  /**
149   * Puts the given int into the preferences table if it doesn't already exist.
150   *
151   * @param key The key
152   * @param value The value
153   */
154  public static void initInt(String key, int value) {
155    NetworkTableEntry entry = m_table.getEntry(key);
156    entry.setDefaultDouble(value);
157    entry.setPersistent();
158  }
159
160  /**
161   * Puts the given double into the preferences table.
162   *
163   * @param key the key
164   * @param value the value
165   */
166  public static void setDouble(String key, double value) {
167    NetworkTableEntry entry = m_table.getEntry(key);
168    entry.setDouble(value);
169    entry.setPersistent();
170  }
171
172  /**
173   * Puts the given double into the preferences table if it doesn't already exist.
174   *
175   * @param key The key
176   * @param value The value
177   */
178  public static void initDouble(String key, double value) {
179    NetworkTableEntry entry = m_table.getEntry(key);
180    entry.setDefaultDouble(value);
181    entry.setPersistent();
182  }
183
184  /**
185   * Puts the given float into the preferences table.
186   *
187   * @param key the key
188   * @param value the value
189   */
190  public static void setFloat(String key, float value) {
191    NetworkTableEntry entry = m_table.getEntry(key);
192    entry.setDouble(value);
193    entry.setPersistent();
194  }
195
196  /**
197   * Puts the given float into the preferences table if it doesn't already exist.
198   *
199   * @param key The key
200   * @param value The value
201   */
202  public static void initFloat(String key, float value) {
203    NetworkTableEntry entry = m_table.getEntry(key);
204    entry.setDefaultDouble(value);
205    entry.setPersistent();
206  }
207
208  /**
209   * Puts the given boolean into the preferences table.
210   *
211   * @param key the key
212   * @param value the value
213   */
214  public static void setBoolean(String key, boolean value) {
215    NetworkTableEntry entry = m_table.getEntry(key);
216    entry.setBoolean(value);
217    entry.setPersistent();
218  }
219
220  /**
221   * Puts the given boolean into the preferences table if it doesn't already exist.
222   *
223   * @param key The key
224   * @param value The value
225   */
226  public static void initBoolean(String key, boolean value) {
227    NetworkTableEntry entry = m_table.getEntry(key);
228    entry.setDefaultBoolean(value);
229    entry.setPersistent();
230  }
231
232  /**
233   * Puts the given long into the preferences table.
234   *
235   * @param key the key
236   * @param value the value
237   */
238  public static void setLong(String key, long value) {
239    NetworkTableEntry entry = m_table.getEntry(key);
240    entry.setInteger(value);
241    entry.setPersistent();
242  }
243
244  /**
245   * Puts the given long into the preferences table if it doesn't already exist.
246   *
247   * @param key The key
248   * @param value The value
249   */
250  public static void initLong(String key, long value) {
251    NetworkTableEntry entry = m_table.getEntry(key);
252    entry.setDefaultInteger(value);
253    entry.setPersistent();
254  }
255
256  /**
257   * Returns whether there is a key with the given name.
258   *
259   * @param key the key
260   * @return if there is a value at the given key
261   */
262  public static boolean containsKey(String key) {
263    return m_table.containsKey(key);
264  }
265
266  /**
267   * Remove a preference.
268   *
269   * @param key the key
270   */
271  public static void remove(String key) {
272    NetworkTableEntry entry = m_table.getEntry(key);
273    entry.clearPersistent();
274    entry.unpublish();
275  }
276
277  /** Remove all preferences. */
278  public static void removeAll() {
279    for (String key : m_table.getKeys()) {
280      if (!".type".equals(key)) {
281        remove(key);
282      }
283    }
284  }
285
286  /**
287   * Returns the string at the given key. If this table does not have a value for that position,
288   * then the given backup value will be returned.
289   *
290   * @param key the key
291   * @param backup the value to return if none exists in the table
292   * @return either the value in the table, or the backup
293   */
294  public static String getString(String key, String backup) {
295    return m_table.getEntry(key).getString(backup);
296  }
297
298  /**
299   * Returns the int at the given key. If this table does not have a value for that position, then
300   * the given backup value will be returned.
301   *
302   * @param key the key
303   * @param backup the value to return if none exists in the table
304   * @return either the value in the table, or the backup
305   */
306  public static int getInt(String key, int backup) {
307    return (int) m_table.getEntry(key).getDouble(backup);
308  }
309
310  /**
311   * Returns the double at the given key. If this table does not have a value for that position,
312   * then the given backup value will be returned.
313   *
314   * @param key the key
315   * @param backup the value to return if none exists in the table
316   * @return either the value in the table, or the backup
317   */
318  public static double getDouble(String key, double backup) {
319    return m_table.getEntry(key).getDouble(backup);
320  }
321
322  /**
323   * Returns the boolean at the given key. If this table does not have a value for that position,
324   * then the given backup value will be returned.
325   *
326   * @param key the key
327   * @param backup the value to return if none exists in the table
328   * @return either the value in the table, or the backup
329   */
330  public static boolean getBoolean(String key, boolean backup) {
331    return m_table.getEntry(key).getBoolean(backup);
332  }
333
334  /**
335   * Returns the float at the given key. If this table does not have a value for that position, then
336   * the given backup value will be returned.
337   *
338   * @param key the key
339   * @param backup the value to return if none exists in the table
340   * @return either the value in the table, or the backup
341   */
342  public static float getFloat(String key, float backup) {
343    return (float) m_table.getEntry(key).getDouble(backup);
344  }
345
346  /**
347   * Returns the long at the given key. If this table does not have a value for that position, then
348   * the given backup value will be returned.
349   *
350   * @param key the key
351   * @param backup the value to return if none exists in the table
352   * @return either the value in the table, or the backup
353   */
354  public static long getLong(String key, long backup) {
355    return m_table.getEntry(key).getInteger(backup);
356  }
357}