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}