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.util.sendable;
006
007import java.lang.ref.WeakReference;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.List;
011import java.util.Map;
012import java.util.WeakHashMap;
013import java.util.function.Consumer;
014import java.util.function.Supplier;
015
016/**
017 * The SendableRegistry class is the public interface for registering sensors and actuators for use
018 * on dashboards and LiveWindow.
019 */
020public class SendableRegistry {
021  private static class Component {
022    Component() {}
023
024    Component(Sendable sendable) {
025      m_sendable = new WeakReference<>(sendable);
026    }
027
028    WeakReference<Sendable> m_sendable;
029    SendableBuilder m_builder;
030    String m_name;
031    String m_subsystem = "Ungrouped";
032    WeakReference<Sendable> m_parent;
033    boolean m_liveWindow;
034    Object[] m_data;
035
036    void setName(String moduleType, int channel) {
037      m_name = moduleType + "[" + channel + "]";
038    }
039
040    void setName(String moduleType, int moduleNumber, int channel) {
041      m_name = moduleType + "[" + moduleNumber + "," + channel + "]";
042    }
043  }
044
045  private static Supplier<SendableBuilder> liveWindowFactory;
046  private static final Map<Object, Component> components = new WeakHashMap<>();
047  private static int nextDataHandle;
048
049  private static Component getOrAdd(Sendable sendable) {
050    Component comp = components.get(sendable);
051    if (comp == null) {
052      comp = new Component(sendable);
053      components.put(sendable, comp);
054    } else {
055      if (comp.m_sendable == null) {
056        comp.m_sendable = new WeakReference<>(sendable);
057      }
058    }
059    return comp;
060  }
061
062  private SendableRegistry() {
063    throw new UnsupportedOperationException("This is a utility class!");
064  }
065
066  /**
067   * Sets the factory for LiveWindow builders.
068   *
069   * @param factory factory function
070   */
071  public static synchronized void setLiveWindowBuilderFactory(Supplier<SendableBuilder> factory) {
072    liveWindowFactory = factory;
073  }
074
075  /**
076   * Adds an object to the registry.
077   *
078   * @param sendable object to add
079   * @param name component name
080   */
081  public static synchronized void add(Sendable sendable, String name) {
082    Component comp = getOrAdd(sendable);
083    comp.m_name = name;
084  }
085
086  /**
087   * Adds an object to the registry.
088   *
089   * @param sendable object to add
090   * @param moduleType A string that defines the module name in the label for the value
091   * @param channel The channel number the device is plugged into
092   */
093  public static synchronized void add(Sendable sendable, String moduleType, int channel) {
094    Component comp = getOrAdd(sendable);
095    comp.setName(moduleType, channel);
096  }
097
098  /**
099   * Adds an object to the registry.
100   *
101   * @param sendable object to add
102   * @param moduleType A string that defines the module name in the label for the value
103   * @param moduleNumber The number of the particular module type
104   * @param channel The channel number the device is plugged into
105   */
106  public static synchronized void add(
107      Sendable sendable, String moduleType, int moduleNumber, int channel) {
108    Component comp = getOrAdd(sendable);
109    comp.setName(moduleType, moduleNumber, channel);
110  }
111
112  /**
113   * Adds an object to the registry.
114   *
115   * @param sendable object to add
116   * @param subsystem subsystem name
117   * @param name component name
118   */
119  public static synchronized void add(Sendable sendable, String subsystem, String name) {
120    Component comp = getOrAdd(sendable);
121    comp.m_name = name;
122    comp.m_subsystem = subsystem;
123  }
124
125  /**
126   * Adds an object to the registry and LiveWindow.
127   *
128   * @param sendable object to add
129   * @param name component name
130   */
131  public static synchronized void addLW(Sendable sendable, String name) {
132    Component comp = getOrAdd(sendable);
133    if (liveWindowFactory != null) {
134      comp.m_builder = liveWindowFactory.get();
135    }
136    comp.m_liveWindow = true;
137    comp.m_name = name;
138  }
139
140  /**
141   * Adds an object to the registry and LiveWindow.
142   *
143   * @param sendable object to add
144   * @param moduleType A string that defines the module name in the label for the value
145   * @param channel The channel number the device is plugged into
146   */
147  public static synchronized void addLW(Sendable sendable, String moduleType, int channel) {
148    Component comp = getOrAdd(sendable);
149    if (liveWindowFactory != null) {
150      comp.m_builder = liveWindowFactory.get();
151    }
152    comp.m_liveWindow = true;
153    comp.setName(moduleType, channel);
154  }
155
156  /**
157   * Adds an object to the registry and LiveWindow.
158   *
159   * @param sendable object to add
160   * @param moduleType A string that defines the module name in the label for the value
161   * @param moduleNumber The number of the particular module type
162   * @param channel The channel number the device is plugged into
163   */
164  public static synchronized void addLW(
165      Sendable sendable, String moduleType, int moduleNumber, int channel) {
166    Component comp = getOrAdd(sendable);
167    if (liveWindowFactory != null) {
168      comp.m_builder = liveWindowFactory.get();
169    }
170    comp.m_liveWindow = true;
171    comp.setName(moduleType, moduleNumber, channel);
172  }
173
174  /**
175   * Adds an object to the registry and LiveWindow.
176   *
177   * @param sendable object to add
178   * @param subsystem subsystem name
179   * @param name component name
180   */
181  public static synchronized void addLW(Sendable sendable, String subsystem, String name) {
182    Component comp = getOrAdd(sendable);
183    if (liveWindowFactory != null) {
184      comp.m_builder = liveWindowFactory.get();
185    }
186    comp.m_liveWindow = true;
187    comp.m_name = name;
188    comp.m_subsystem = subsystem;
189  }
190
191  /**
192   * Adds a child object to an object. Adds the child object to the registry if it's not already
193   * present.
194   *
195   * @param parent parent object
196   * @param child child object
197   */
198  public static synchronized void addChild(Sendable parent, Object child) {
199    Component comp = components.get(child);
200    if (comp == null) {
201      comp = new Component();
202      components.put(child, comp);
203    }
204    comp.m_parent = new WeakReference<>(parent);
205  }
206
207  /**
208   * Removes an object from the registry.
209   *
210   * @param sendable object to remove
211   * @return true if the object was removed; false if it was not present
212   */
213  public static synchronized boolean remove(Sendable sendable) {
214    return components.remove(sendable) != null;
215  }
216
217  /**
218   * Determines if an object is in the registry.
219   *
220   * @param sendable object to check
221   * @return True if in registry, false if not.
222   */
223  public static synchronized boolean contains(Sendable sendable) {
224    return components.containsKey(sendable);
225  }
226
227  /**
228   * Gets the name of an object.
229   *
230   * @param sendable object
231   * @return Name (empty if object is not in registry)
232   */
233  public static synchronized String getName(Sendable sendable) {
234    Component comp = components.get(sendable);
235    if (comp == null) {
236      return "";
237    }
238    return comp.m_name;
239  }
240
241  /**
242   * Sets the name of an object.
243   *
244   * @param sendable object
245   * @param name name
246   */
247  public static synchronized void setName(Sendable sendable, String name) {
248    Component comp = components.get(sendable);
249    if (comp != null) {
250      comp.m_name = name;
251    }
252  }
253
254  /**
255   * Sets the name of an object with a channel number.
256   *
257   * @param sendable object
258   * @param moduleType A string that defines the module name in the label for the value
259   * @param channel The channel number the device is plugged into
260   */
261  public static synchronized void setName(Sendable sendable, String moduleType, int channel) {
262    Component comp = components.get(sendable);
263    if (comp != null) {
264      comp.setName(moduleType, channel);
265    }
266  }
267
268  /**
269   * Sets the name of an object with a module and channel number.
270   *
271   * @param sendable object
272   * @param moduleType A string that defines the module name in the label for the value
273   * @param moduleNumber The number of the particular module type
274   * @param channel The channel number the device is plugged into
275   */
276  public static synchronized void setName(
277      Sendable sendable, String moduleType, int moduleNumber, int channel) {
278    Component comp = components.get(sendable);
279    if (comp != null) {
280      comp.setName(moduleType, moduleNumber, channel);
281    }
282  }
283
284  /**
285   * Sets both the subsystem name and device name of an object.
286   *
287   * @param sendable object
288   * @param subsystem subsystem name
289   * @param name device name
290   */
291  public static synchronized void setName(Sendable sendable, String subsystem, String name) {
292    Component comp = components.get(sendable);
293    if (comp != null) {
294      comp.m_name = name;
295      comp.m_subsystem = subsystem;
296    }
297  }
298
299  /**
300   * Gets the subsystem name of an object.
301   *
302   * @param sendable object
303   * @return Subsystem name (empty if object is not in registry)
304   */
305  public static synchronized String getSubsystem(Sendable sendable) {
306    Component comp = components.get(sendable);
307    if (comp == null) {
308      return "";
309    }
310    return comp.m_subsystem;
311  }
312
313  /**
314   * Sets the subsystem name of an object.
315   *
316   * @param sendable object
317   * @param subsystem subsystem name
318   */
319  public static synchronized void setSubsystem(Sendable sendable, String subsystem) {
320    Component comp = components.get(sendable);
321    if (comp != null) {
322      comp.m_subsystem = subsystem;
323    }
324  }
325
326  /**
327   * Gets a unique handle for setting/getting data with setData() and getData().
328   *
329   * @return Handle
330   */
331  public static synchronized int getDataHandle() {
332    return nextDataHandle++;
333  }
334
335  /**
336   * Associates arbitrary data with an object in the registry.
337   *
338   * @param sendable object
339   * @param handle data handle returned by getDataHandle()
340   * @param data data to set
341   * @return Previous data (may be null)
342   */
343  public static synchronized Object setData(Sendable sendable, int handle, Object data) {
344    Component comp = components.get(sendable);
345    if (comp == null) {
346      return null;
347    }
348    Object rv = null;
349    if (comp.m_data == null) {
350      comp.m_data = new Object[handle + 1];
351    } else if (handle < comp.m_data.length) {
352      rv = comp.m_data[handle];
353    } else {
354      comp.m_data = Arrays.copyOf(comp.m_data, handle + 1);
355    }
356    comp.m_data[handle] = data;
357    return rv;
358  }
359
360  /**
361   * Gets arbitrary data associated with an object in the registry.
362   *
363   * @param sendable object
364   * @param handle data handle returned by getDataHandle()
365   * @return data (may be null if none associated)
366   */
367  public static synchronized Object getData(Sendable sendable, int handle) {
368    Component comp = components.get(sendable);
369    if (comp == null || comp.m_data == null || handle >= comp.m_data.length) {
370      return null;
371    }
372    return comp.m_data[handle];
373  }
374
375  /**
376   * Enables LiveWindow for an object.
377   *
378   * @param sendable object
379   */
380  public static synchronized void enableLiveWindow(Sendable sendable) {
381    Component comp = components.get(sendable);
382    if (comp != null) {
383      comp.m_liveWindow = true;
384    }
385  }
386
387  /**
388   * Disables LiveWindow for an object.
389   *
390   * @param sendable object
391   */
392  public static synchronized void disableLiveWindow(Sendable sendable) {
393    Component comp = components.get(sendable);
394    if (comp != null) {
395      comp.m_liveWindow = false;
396    }
397  }
398
399  /**
400   * Publishes an object in the registry to a builder.
401   *
402   * @param sendable object
403   * @param builder sendable builder
404   */
405  public static synchronized void publish(Sendable sendable, SendableBuilder builder) {
406    Component comp = getOrAdd(sendable);
407    if (comp.m_builder != null) {
408      comp.m_builder.clearProperties();
409    }
410    comp.m_builder = builder; // clear any current builder
411    sendable.initSendable(comp.m_builder);
412    comp.m_builder.update();
413  }
414
415  /**
416   * Updates network table information from an object.
417   *
418   * @param sendable object
419   */
420  public static synchronized void update(Sendable sendable) {
421    Component comp = components.get(sendable);
422    if (comp != null && comp.m_builder != null) {
423      comp.m_builder.update();
424    }
425  }
426
427  /** Data passed to foreachLiveWindow() callback function. */
428  public static class CallbackData {
429    /** Sendable object. */
430    @SuppressWarnings("MemberName")
431    public Sendable sendable;
432
433    /** Name. */
434    @SuppressWarnings("MemberName")
435    public String name;
436
437    /** Subsystem. */
438    @SuppressWarnings("MemberName")
439    public String subsystem;
440
441    /** Parent sendable object. */
442    @SuppressWarnings("MemberName")
443    public Sendable parent;
444
445    /** Data stored in object with setData(). Update this to change the data. */
446    @SuppressWarnings("MemberName")
447    public Object data;
448
449    /** Sendable builder for the sendable. */
450    @SuppressWarnings("MemberName")
451    public SendableBuilder builder;
452  }
453
454  // As foreachLiveWindow is single threaded, cache the components it
455  // iterates over to avoid risk of ConcurrentModificationException
456  private static List<Component> foreachComponents = new ArrayList<>();
457
458  /**
459   * Iterates over LiveWindow-enabled objects in the registry. It is *not* safe to call other
460   * SendableRegistry functions from the callback.
461   *
462   * @param dataHandle data handle to get data object passed to callback
463   * @param callback function to call for each object
464   */
465  @SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.AvoidReassigningCatchVariables"})
466  public static synchronized void foreachLiveWindow(
467      int dataHandle, Consumer<CallbackData> callback) {
468    CallbackData cbdata = new CallbackData();
469    foreachComponents.clear();
470    foreachComponents.addAll(components.values());
471    for (Component comp : foreachComponents) {
472      if (comp.m_builder == null || comp.m_sendable == null) {
473        continue;
474      }
475      cbdata.sendable = comp.m_sendable.get();
476      if (cbdata.sendable != null && comp.m_liveWindow) {
477        cbdata.name = comp.m_name;
478        cbdata.subsystem = comp.m_subsystem;
479        if (comp.m_parent != null) {
480          cbdata.parent = comp.m_parent.get();
481        } else {
482          cbdata.parent = null;
483        }
484        if (comp.m_data != null && dataHandle < comp.m_data.length) {
485          cbdata.data = comp.m_data[dataHandle];
486        } else {
487          cbdata.data = null;
488        }
489        cbdata.builder = comp.m_builder;
490        try {
491          callback.accept(cbdata);
492        } catch (Throwable throwable) {
493          Throwable cause = throwable.getCause();
494          if (cause != null) {
495            throwable = cause;
496          }
497          System.err.println("Unhandled exception calling LiveWindow for " + comp.m_name + ": ");
498          throwable.printStackTrace();
499          comp.m_liveWindow = false;
500        }
501        if (cbdata.data != null) {
502          if (comp.m_data == null) {
503            comp.m_data = new Object[dataHandle + 1];
504          } else if (dataHandle >= comp.m_data.length) {
505            comp.m_data = Arrays.copyOf(comp.m_data, dataHandle + 1);
506          }
507          comp.m_data[dataHandle] = cbdata.data;
508        }
509      }
510    }
511    foreachComponents.clear();
512  }
513}