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