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.Arrays;
009import java.util.Map;
010import java.util.WeakHashMap;
011
012/**
013 * The SendableRegistry class is the public interface for registering sensors and actuators for use
014 * on dashboards.
015 */
016@SuppressWarnings("PMD.AvoidCatchingGenericException")
017public final class SendableRegistry {
018  private static class Component implements AutoCloseable {
019    Component() {}
020
021    Component(Sendable sendable) {
022      m_sendable = new WeakReference<>(sendable);
023    }
024
025    @Override
026    public void close() throws Exception {
027      m_builder.close();
028      for (AutoCloseable data : m_data) {
029        if (data != null) {
030          data.close();
031        }
032      }
033    }
034
035    WeakReference<Sendable> m_sendable;
036    SendableBuilder m_builder;
037    String m_name;
038    String m_subsystem = "Ungrouped";
039    AutoCloseable[] m_data;
040
041    void setName(String moduleType, int channel) {
042      m_name = moduleType + "[" + channel + "]";
043    }
044
045    void setName(String moduleType, int moduleNumber, int channel) {
046      m_name = moduleType + "[" + moduleNumber + "," + channel + "]";
047    }
048  }
049
050  private static final Map<Object, Component> components = new WeakHashMap<>();
051  private static int nextDataHandle;
052
053  private static Component getOrAdd(Sendable sendable) {
054    Component comp = components.get(sendable);
055    if (comp == null) {
056      comp = new Component(sendable);
057      components.put(sendable, comp);
058    } else {
059      if (comp.m_sendable == null) {
060        comp.m_sendable = new WeakReference<>(sendable);
061      }
062    }
063    return comp;
064  }
065
066  private SendableRegistry() {
067    throw new UnsupportedOperationException("This is a utility class!");
068  }
069
070  /**
071   * Adds an object to the registry.
072   *
073   * @param sendable object to add
074   * @param name component name
075   */
076  public static synchronized void add(Sendable sendable, String name) {
077    Component comp = getOrAdd(sendable);
078    comp.m_name = name;
079  }
080
081  /**
082   * Adds an object to the registry.
083   *
084   * @param sendable object to add
085   * @param moduleType A string that defines the module name in the label for the value
086   * @param channel The channel number the device is plugged into
087   */
088  public static synchronized void add(Sendable sendable, String moduleType, int channel) {
089    Component comp = getOrAdd(sendable);
090    comp.setName(moduleType, channel);
091  }
092
093  /**
094   * Adds an object to the registry.
095   *
096   * @param sendable object to add
097   * @param moduleType A string that defines the module name in the label for the value
098   * @param moduleNumber The number of the particular module type
099   * @param channel The channel number the device is plugged into
100   */
101  public static synchronized void add(
102      Sendable sendable, String moduleType, int moduleNumber, int channel) {
103    Component comp = getOrAdd(sendable);
104    comp.setName(moduleType, moduleNumber, channel);
105  }
106
107  /**
108   * Adds an object to the registry.
109   *
110   * @param sendable object to add
111   * @param subsystem subsystem name
112   * @param name component name
113   */
114  public static synchronized void add(Sendable sendable, String subsystem, String name) {
115    Component comp = getOrAdd(sendable);
116    comp.m_name = name;
117    comp.m_subsystem = subsystem;
118  }
119
120  /**
121   * Adds a child object to an object. Adds the child object to the registry if it's not already
122   * present.
123   *
124   * @param parent parent object
125   * @param child child object
126   */
127  public static synchronized void addChild(Sendable parent, Object child) {
128    Component comp = components.get(child);
129    if (comp == null) {
130      comp = new Component();
131      components.put(child, comp);
132    }
133    // comp.m_parent = new WeakReference<>(parent);
134  }
135
136  /**
137   * Removes an object from the registry.
138   *
139   * @param sendable object to remove
140   * @return true if the object was removed; false if it was not present
141   */
142  public static synchronized boolean remove(Sendable sendable) {
143    Component comp = components.remove(sendable);
144    if (comp != null) {
145      try {
146        comp.close();
147      } catch (Exception e) {
148        // ignore
149      }
150    }
151    return comp != null;
152  }
153
154  /**
155   * Determines if an object is in the registry.
156   *
157   * @param sendable object to check
158   * @return True if in registry, false if not.
159   */
160  public static synchronized boolean contains(Sendable sendable) {
161    return components.containsKey(sendable);
162  }
163
164  /**
165   * Gets the name of an object.
166   *
167   * @param sendable object
168   * @return Name (empty if object is not in registry)
169   */
170  public static synchronized String getName(Sendable sendable) {
171    Component comp = components.get(sendable);
172    if (comp == null) {
173      return "";
174    }
175    return comp.m_name;
176  }
177
178  /**
179   * Sets the name of an object.
180   *
181   * @param sendable object
182   * @param name name
183   */
184  public static synchronized void setName(Sendable sendable, String name) {
185    Component comp = components.get(sendable);
186    if (comp != null) {
187      comp.m_name = name;
188    }
189  }
190
191  /**
192   * Sets the name of an object with a channel number.
193   *
194   * @param sendable object
195   * @param moduleType A string that defines the module name in the label for the value
196   * @param channel The channel number the device is plugged into
197   */
198  public static synchronized void setName(Sendable sendable, String moduleType, int channel) {
199    Component comp = components.get(sendable);
200    if (comp != null) {
201      comp.setName(moduleType, channel);
202    }
203  }
204
205  /**
206   * Sets the name of an object with a module and channel number.
207   *
208   * @param sendable object
209   * @param moduleType A string that defines the module name in the label for the value
210   * @param moduleNumber The number of the particular module type
211   * @param channel The channel number the device is plugged into
212   */
213  public static synchronized void setName(
214      Sendable sendable, String moduleType, int moduleNumber, int channel) {
215    Component comp = components.get(sendable);
216    if (comp != null) {
217      comp.setName(moduleType, moduleNumber, channel);
218    }
219  }
220
221  /**
222   * Sets both the subsystem name and device name of an object.
223   *
224   * @param sendable object
225   * @param subsystem subsystem name
226   * @param name device name
227   */
228  public static synchronized void setName(Sendable sendable, String subsystem, String name) {
229    Component comp = components.get(sendable);
230    if (comp != null) {
231      comp.m_name = name;
232      comp.m_subsystem = subsystem;
233    }
234  }
235
236  /**
237   * Gets the subsystem name of an object.
238   *
239   * @param sendable object
240   * @return Subsystem name (empty if object is not in registry)
241   */
242  public static synchronized String getSubsystem(Sendable sendable) {
243    Component comp = components.get(sendable);
244    if (comp == null) {
245      return "";
246    }
247    return comp.m_subsystem;
248  }
249
250  /**
251   * Sets the subsystem name of an object.
252   *
253   * @param sendable object
254   * @param subsystem subsystem name
255   */
256  public static synchronized void setSubsystem(Sendable sendable, String subsystem) {
257    Component comp = components.get(sendable);
258    if (comp != null) {
259      comp.m_subsystem = subsystem;
260    }
261  }
262
263  /**
264   * Gets a unique handle for setting/getting data with setData() and getData().
265   *
266   * @return Handle
267   */
268  public static synchronized int getDataHandle() {
269    return nextDataHandle++;
270  }
271
272  /**
273   * Associates arbitrary data with an object in the registry.
274   *
275   * @param sendable object
276   * @param handle data handle returned by getDataHandle()
277   * @param data data to set
278   * @return Previous data (may be null). If non-null, caller is responsible for calling close().
279   */
280  @SuppressWarnings("PMD.CompareObjectsWithEquals")
281  public static synchronized AutoCloseable setData(
282      Sendable sendable, int handle, AutoCloseable data) {
283    Component comp = components.get(sendable);
284    if (comp == null) {
285      return null;
286    }
287    AutoCloseable rv = null;
288    if (comp.m_data == null) {
289      comp.m_data = new AutoCloseable[handle + 1];
290    } else if (handle < comp.m_data.length) {
291      rv = comp.m_data[handle];
292    } else {
293      comp.m_data = Arrays.copyOf(comp.m_data, handle + 1);
294    }
295    if (comp.m_data[handle] != data) {
296      if (comp.m_data[handle] != null) {
297        try {
298          comp.m_data[handle].close();
299        } catch (Exception e) {
300          // ignore
301        }
302      }
303      comp.m_data[handle] = data;
304    }
305    return rv;
306  }
307
308  /**
309   * Gets arbitrary data associated with an object in the registry.
310   *
311   * @param sendable object
312   * @param handle data handle returned by getDataHandle()
313   * @return data (may be null if none associated)
314   */
315  public static synchronized Object getData(Sendable sendable, int handle) {
316    Component comp = components.get(sendable);
317    if (comp == null || comp.m_data == null || handle >= comp.m_data.length) {
318      return null;
319    }
320    return comp.m_data[handle];
321  }
322
323  /**
324   * Publishes an object in the registry to a builder.
325   *
326   * @param sendable object
327   * @param builder sendable builder
328   */
329  public static synchronized void publish(Sendable sendable, SendableBuilder builder) {
330    Component comp = getOrAdd(sendable);
331    if (comp.m_builder != null) {
332      try {
333        comp.m_builder.close();
334      } catch (Exception e) {
335        // ignore
336      }
337    }
338    comp.m_builder = builder; // clear any current builder
339    sendable.initSendable(comp.m_builder);
340    comp.m_builder.update();
341  }
342
343  /**
344   * Updates network table information from an object.
345   *
346   * @param sendable object
347   */
348  public static synchronized void update(Sendable sendable) {
349    Component comp = components.get(sendable);
350    if (comp != null && comp.m_builder != null) {
351      comp.m_builder.update();
352    }
353  }
354}