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