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