001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2017-2018 FIRST. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj.smartdashboard;
009
010import edu.wpi.first.networktables.EntryListenerFlags;
011import edu.wpi.first.networktables.NetworkTable;
012import edu.wpi.first.networktables.NetworkTableEntry;
013import edu.wpi.first.networktables.NetworkTableValue;
014import java.util.function.BooleanSupplier;
015import java.util.function.Consumer;
016import java.util.function.DoubleConsumer;
017import java.util.function.DoubleSupplier;
018import java.util.function.Function;
019import java.util.function.Supplier;
020import java.util.ArrayList;
021import java.util.List;
022
023public class SendableBuilderImpl implements SendableBuilder {
024  private static class Property {
025    Property(NetworkTable table, String key) {
026      m_entry = table.getEntry(key);
027    }
028
029    @Override
030    @SuppressWarnings("NoFinalizer")
031    public synchronized void finalize() {
032      stopListener();
033    }
034
035    void startListener() {
036      if (m_entry.isValid() && m_listener == 0 && m_createListener != null) {
037        m_listener = m_createListener.apply(m_entry);
038      }
039    }
040
041    void stopListener() {
042      if (m_entry.isValid() && m_listener != 0) {
043        m_entry.removeListener(m_listener);
044        m_listener = 0;
045      }
046    }
047
048    final NetworkTableEntry m_entry;
049    int m_listener = 0;
050    Consumer<NetworkTableEntry> m_update;
051    Function<NetworkTableEntry, Integer> m_createListener;
052  }
053
054  private final List<Property> m_properties = new ArrayList<>();
055  private Runnable m_safeState;
056  private Runnable m_updateTable;
057  private NetworkTable m_table;
058
059  /**
060   * Set the network table.  Must be called prior to any Add* functions being
061   * called.
062   * @param table Network table
063   */
064  public void setTable(NetworkTable table) {
065    m_table = table;
066  }
067
068  /**
069   * Get the network table.
070   * @return The network table
071   */
072  public NetworkTable getTable() {
073    return m_table;
074  }
075
076  /**
077   * Update the network table values by calling the getters for all properties.
078   */
079  public void updateTable() {
080    for (Property property : m_properties) {
081      if (property.m_update != null) {
082        property.m_update.accept(property.m_entry);
083      }
084    }
085    if (m_updateTable != null) {
086      m_updateTable.run();
087    }
088  }
089
090  /**
091   * Hook setters for all properties.
092   */
093  public void startListeners() {
094    for (Property property : m_properties) {
095      property.startListener();
096    }
097  }
098
099  /**
100   * Unhook setters for all properties.
101   */
102  public void stopListeners() {
103    for (Property property : m_properties) {
104      property.stopListener();
105    }
106  }
107
108  /**
109   * Start LiveWindow mode by hooking the setters for all properties.  Also
110   * calls the safeState function if one was provided.
111   */
112  public void startLiveWindowMode() {
113    if (m_safeState != null) {
114      m_safeState.run();
115    }
116    startListeners();
117  }
118
119  /**
120   * Stop LiveWindow mode by unhooking the setters for all properties.  Also
121   * calls the safeState function if one was provided.
122   */
123  public void stopLiveWindowMode() {
124    stopListeners();
125    if (m_safeState != null) {
126      m_safeState.run();
127    }
128  }
129
130  /**
131   * Set the string representation of the named data type that will be used
132   * by the smart dashboard for this sendable.
133   *
134   * @param type    data type
135   */
136  @Override
137  public void setSmartDashboardType(String type) {
138    m_table.getEntry(".type").setString(type);
139  }
140
141  /**
142   * Set the function that should be called to set the Sendable into a safe
143   * state.  This is called when entering and exiting Live Window mode.
144   *
145   * @param func    function
146   */
147  @Override
148  public void setSafeState(Runnable func) {
149    m_safeState = func;
150  }
151
152  /**
153   * Set the function that should be called to update the network table
154   * for things other than properties.  Note this function is not passed
155   * the network table object; instead it should use the entry handles
156   * returned by getEntry().
157   *
158   * @param func    function
159   */
160  @Override
161  public void setUpdateTable(Runnable func) {
162    m_updateTable = func;
163  }
164
165  /**
166   * Add a property without getters or setters.  This can be used to get
167   * entry handles for the function called by setUpdateTable().
168   *
169   * @param key   property name
170   * @return Network table entry
171   */
172  @Override
173  public NetworkTableEntry getEntry(String key) {
174    return m_table.getEntry(key);
175  }
176
177  /**
178   * Add a boolean property.
179   *
180   * @param key     property name
181   * @param getter  getter function (returns current value)
182   * @param setter  setter function (sets new value)
183   */
184  @Override
185  public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
186    Property property = new Property(m_table, key);
187    if (getter != null) {
188      property.m_update = (entry) -> entry.setBoolean(getter.getAsBoolean());
189    }
190    if (setter != null) {
191      property.m_createListener = (entry) -> entry.addListener((event) -> {
192        if (event.value.isBoolean()) {
193          setter.accept(event.value.getBoolean());
194        }
195      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
196    }
197    m_properties.add(property);
198  }
199
200  /**
201   * Add a double property.
202   *
203   * @param key     property name
204   * @param getter  getter function (returns current value)
205   * @param setter  setter function (sets new value)
206   */
207  @Override
208  public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
209    Property property = new Property(m_table, key);
210    if (getter != null) {
211      property.m_update = (entry) -> entry.setDouble(getter.getAsDouble());
212    }
213    if (setter != null) {
214      property.m_createListener = (entry) -> entry.addListener((event) -> {
215        if (event.value.isDouble()) {
216          setter.accept(event.value.getDouble());
217        }
218      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
219    }
220    m_properties.add(property);
221  }
222
223  /**
224   * Add a string property.
225   *
226   * @param key     property name
227   * @param getter  getter function (returns current value)
228   * @param setter  setter function (sets new value)
229   */
230  @Override
231  public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
232    Property property = new Property(m_table, key);
233    if (getter != null) {
234      property.m_update = (entry) -> entry.setString(getter.get());
235    }
236    if (setter != null) {
237      property.m_createListener = (entry) -> entry.addListener((event) -> {
238        if (event.value.isString()) {
239          setter.accept(event.value.getString());
240        }
241      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
242    }
243    m_properties.add(property);
244  }
245
246  /**
247   * Add a boolean array property.
248   *
249   * @param key     property name
250   * @param getter  getter function (returns current value)
251   * @param setter  setter function (sets new value)
252   */
253  @Override
254  public void addBooleanArrayProperty(String key, Supplier<boolean[]> getter,
255                                      Consumer<boolean[]> setter) {
256    Property property = new Property(m_table, key);
257    if (getter != null) {
258      property.m_update = (entry) -> entry.setBooleanArray(getter.get());
259    }
260    if (setter != null) {
261      property.m_createListener = (entry) -> entry.addListener((event) -> {
262        if (event.value.isBooleanArray()) {
263          setter.accept(event.value.getBooleanArray());
264        }
265      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
266    }
267    m_properties.add(property);
268  }
269
270  /**
271   * Add a double array property.
272   *
273   * @param key     property name
274   * @param getter  getter function (returns current value)
275   * @param setter  setter function (sets new value)
276   */
277  @Override
278  public void addDoubleArrayProperty(String key, Supplier<double[]> getter,
279                                     Consumer<double[]> setter) {
280    Property property = new Property(m_table, key);
281    if (getter != null) {
282      property.m_update = (entry) -> entry.setDoubleArray(getter.get());
283    }
284    if (setter != null) {
285      property.m_createListener = (entry) -> entry.addListener((event) -> {
286        if (event.value.isDoubleArray()) {
287          setter.accept(event.value.getDoubleArray());
288        }
289      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
290    }
291    m_properties.add(property);
292  }
293
294  /**
295   * Add a string array property.
296   *
297   * @param key     property name
298   * @param getter  getter function (returns current value)
299   * @param setter  setter function (sets new value)
300   */
301  @Override
302  public void addStringArrayProperty(String key, Supplier<String[]> getter,
303                                     Consumer<String[]> setter) {
304    Property property = new Property(m_table, key);
305    if (getter != null) {
306      property.m_update = (entry) -> entry.setStringArray(getter.get());
307    }
308    if (setter != null) {
309      property.m_createListener = (entry) -> entry.addListener((event) -> {
310        if (event.value.isStringArray()) {
311          setter.accept(event.value.getStringArray());
312        }
313      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
314    }
315    m_properties.add(property);
316  }
317
318  /**
319   * Add a raw property.
320   *
321   * @param key     property name
322   * @param getter  getter function (returns current value)
323   * @param setter  setter function (sets new value)
324   */
325  @Override
326  public void addRawProperty(String key, Supplier<byte[]> getter, Consumer<byte[]> setter) {
327    Property property = new Property(m_table, key);
328    if (getter != null) {
329      property.m_update = (entry) -> entry.setRaw(getter.get());
330    }
331    if (setter != null) {
332      property.m_createListener = (entry) -> entry.addListener((event) -> {
333        if (event.value.isRaw()) {
334          setter.accept(event.value.getRaw());
335        }
336      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
337    }
338    m_properties.add(property);
339  }
340
341  /**
342   * Add a NetworkTableValue property.
343   *
344   * @param key     property name
345   * @param getter  getter function (returns current value)
346   * @param setter  setter function (sets new value)
347   */
348  @Override
349  public void addValueProperty(String key, Supplier<NetworkTableValue> getter,
350                               Consumer<NetworkTableValue> setter) {
351    Property property = new Property(m_table, key);
352    if (getter != null) {
353      property.m_update = (entry) -> entry.setValue(getter.get());
354    }
355    if (setter != null) {
356      property.m_createListener = (entry) -> entry.addListener((event) -> {
357        setter.accept(event.value);
358      }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate);
359    }
360    m_properties.add(property);
361  }
362}