001package jmri; 002 003import java.beans.PropertyChangeListener; 004import java.beans.PropertyChangeSupport; 005import java.io.BufferedWriter; 006import java.io.File; 007import java.io.FileWriter; 008import java.io.PrintWriter; 009import java.lang.reflect.InvocationTargetException; 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.HashMap; 013import java.util.HashSet; 014import java.util.List; 015import java.util.Map; 016import java.util.Optional; 017import java.util.ServiceLoader; 018import java.util.Set; 019 020import javax.annotation.CheckForNull; 021import javax.annotation.Nonnull; 022 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026import jmri.util.ThreadingUtil; 027 028/** 029 * Provides methods for locating various interface implementations. These form 030 * the base for locating JMRI objects, including the key managers. 031 * <p> 032 * The structural goal is to have the jmri package not depend on lower 033 * packages, with the implementations still available 034 * at run-time through the InstanceManager. 035 * <p> 036 * To retrieve the default object of a specific type, do 037 * {@link InstanceManager#getDefault} where the argument is e.g. 038 * "SensorManager.class". In other words, you ask for the default object of a 039 * particular type. Note that this call is intended to be used in the usual case 040 * of requiring the object to function; it will log a message if there isn't 041 * such an object. If that's routine, then use the 042 * {@link InstanceManager#getNullableDefault} method instead. 043 * <p> 044 * Multiple items can be held, and are retrieved as a list with 045 * {@link InstanceManager#getList}. 046 * <p> 047 * If a specific item is needed, e.g. one that has been constructed via a 048 * complex process during startup, it should be installed with 049 * {@link InstanceManager#store}. 050 * <p> 051 * If it is desirable for the InstanceManager to create an object on first 052 * request, have that object's class implement the 053 * {@link InstanceManagerAutoDefault} flag interface. The InstanceManager will 054 * then construct a default object via the no-argument constructor when one is 055 * first requested. 056 * <p> 057 * For initialization of more complex default objects, see the 058 * {@link InstanceInitializer} mechanism and its default implementation in 059 * {@link jmri.managers.DefaultInstanceInitializer}. 060 * <p> 061 * Implement the {@link InstanceManagerAutoInitialize} interface when default 062 * objects need to be initialized after the default instance has been 063 * constructed and registered with the InstanceManager. This will allow 064 * references to the default instance during initialization to work as expected. 065 * <hr> 066 * This file is part of JMRI. 067 * <p> 068 * JMRI is free software; you can redistribute it and/or modify it under the 069 * terms of version 2 of the GNU General Public License as published by the Free 070 * Software Foundation. See the "COPYING" file for a copy of this license. 071 * <p> 072 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 073 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 074 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 075 * 076 * @author Bob Jacobsen Copyright (C) 2001, 2008, 2013, 2016 077 * @author Matthew Harris copyright (c) 2009 078 */ 079public final class InstanceManager { 080 081 // data members to hold contact with the property listeners 082 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 083 private final Map<Class<?>, List<Object>> managerLists = Collections.synchronizedMap(new HashMap<>()); 084 private final HashMap<Class<?>, InstanceInitializer> initializers = new HashMap<>(); 085 private final HashMap<Class<?>, StateHolder> initState = new HashMap<>(); 086 087 /** 088 * Store an object of a particular type for later retrieval via 089 * {@link #getDefault} or {@link #getList}. 090 * 091 * @param <T> The type of the class 092 * @param item The object of type T to be stored 093 * @param type The class Object for the item's type. This will be used as 094 * the key to retrieve the object later. 095 */ 096 public static <T> void store(@Nonnull T item, @Nonnull Class<T> type) { 097 log.debug("Store item of type {}", type.getName()); 098 if (item == null) { 099 NullPointerException npe = new NullPointerException(); 100 log.error("Should not store null value of type {}", type.getName()); 101 throw npe; 102 } 103 List<T> l = getList(type); 104 l.add(item); 105 getDefault().pcs.fireIndexedPropertyChange(getListPropertyName(type), l.indexOf(item), null, item); 106 } 107 108 109 /** 110 * Store an object of a particular type for later retrieval via 111 * {@link #getDefault} or {@link #getList}. 112 *<p> 113 * {@link #store} is preferred to this method because it does type 114 * checking at compile time. In (rare) cases that's not possible, 115 * and run-time checking is required. 116 * 117 * @param <T> The type of the class 118 * @param item The object of type T to be stored 119 * @param type The class Object for the item's type. This will be used as 120 * the key to retrieve the object later. 121 */ 122 public static <T> void storeUnchecked(@Nonnull Object item, @Nonnull Class<T> type) { 123 log.debug("Store item of type {}", type.getName()); 124 if (item == null) { 125 NullPointerException npe = new NullPointerException(); 126 log.error("Should not store null value of type {}", type.getName()); 127 throw npe; 128 } 129 List<T> l = getList(type); 130 try { 131 l.add(type.cast(item)); 132 getDefault().pcs.fireIndexedPropertyChange(getListPropertyName(type), l.indexOf(item), null, item); 133 } catch (ClassCastException ex) { 134 log.error("Attempt to do unchecked store with invalid type {}", type, ex); 135 } 136 } 137 138 /** 139 * Retrieve a list of all objects of type T that were registered with 140 * {@link #store}. 141 * 142 * @param <T> The type of the class 143 * @param type The class Object for the items' type. 144 * @return A list of type Objects registered with the manager or an empty 145 * list. 146 */ 147 @Nonnull 148 public static <T> List<T> getList(@Nonnull Class<T> type) { 149 return getDefault().getInstances(type); 150 } 151 152 /** 153 * Retrieve a list of all objects of a specific type that were registered with 154 * {@link #store}. 155 * 156 * Intended for use with i.e. scripts where access to the class type is inconvenient. 157 * In Java code where typing is enforced, use {@link #getList(Class)}. 158 * 159 * @param className Fully qualified class name 160 * @return A list of type Objects registered with the manager or an empty 161 * list. 162 * @throws IllegalArgumentException if the named class doesn't exist 163 */ 164 @Nonnull 165 public static List<Object> getList(@Nonnull String className) { 166 return getDefault().getInstances(className); 167 } 168 169 170 /** 171 * Deregister all objects of a particular type. 172 * 173 * @param <T> The type of the class 174 * @param type The class Object for the items to be removed. 175 */ 176 public static <T> void reset(@Nonnull Class<T> type) { 177 getDefault().clear(type); 178 } 179 180 /** 181 * Remove an object of a particular type that had earlier been registered 182 * with {@link #store}. If item was previously registered, this will remove 183 * item and fire an indexed property change event for the property matching 184 * the output of {@link #getListPropertyName(java.lang.Class)} for type. 185 * <p> 186 * This is the static access to 187 * {@link #remove(java.lang.Object, java.lang.Class)}. 188 * 189 * @param <T> The type of the class 190 * @param item The object of type T to be deregistered 191 * @param type The class Object for the item's type 192 */ 193 public static <T> void deregister(@Nonnull T item, @Nonnull Class<T> type) { 194 getDefault().remove(item, type); 195 } 196 197 /** 198 * Remove an object of a particular type that had earlier been registered 199 * with {@link #store}. If item was previously registered, this will remove 200 * item and fire an indexed property change event for the property matching 201 * the output of {@link #getListPropertyName(java.lang.Class)} for type. 202 * 203 * @param <T> The type of the class 204 * @param item The object of type T to be deregistered 205 * @param type The class Object for the item's type 206 */ 207 public <T> void remove(@Nonnull T item, @Nonnull Class<T> type) { 208 log.debug("Remove item type {}", type.getName()); 209 List<T> l = getList(type); 210 int index = l.indexOf(item); 211 if (index != -1) { // -1 means items was not in list, and therefore, not registered 212 l.remove(item); 213 if (item instanceof Disposable) { 214 dispose((Disposable) item); 215 } 216 } 217 // if removing last item, re-initialize later 218 if (l.isEmpty()) { 219 setInitializationState(type, InitializationState.NOTSET); 220 } 221 if (index != -1) { // -1 means items was not in list, and therefore, not registered 222 // fire property change last 223 pcs.fireIndexedPropertyChange(getListPropertyName(type), index, item, null); 224 } 225 } 226 227 /** 228 * Retrieve the last object of type T that was registered with 229 * {@link #store(java.lang.Object, java.lang.Class) }. 230 * <p> 231 * Unless specifically set, the default is the last object stored, see the 232 * {@link #setDefault(java.lang.Class, java.lang.Object) } method. 233 * <p> 234 * In some cases, InstanceManager can create the object the first time it's 235 * requested. For more on that, see the class comment. 236 * <p> 237 * In most cases, system configuration assures the existence of a default 238 * object, so this method will log and throw an exception if one doesn't 239 * exist. Use {@link #getNullableDefault(java.lang.Class)} or 240 * {@link #getOptionalDefault(java.lang.Class)} if the default is not 241 * guaranteed to exist. 242 * 243 * @param <T> The type of the class 244 * @param type The class Object for the item's type 245 * @return The default object for type 246 * @throws NullPointerException if no default object for type exists 247 * @see #getNullableDefault(java.lang.Class) 248 * @see #getOptionalDefault(java.lang.Class) 249 */ 250 @Nonnull 251 public static <T> T getDefault(@Nonnull Class<T> type) { 252 log.trace("getDefault of type {}", type.getName()); 253 T object = InstanceManager.getNullableDefault(type); 254 if (object == null) { 255 throw new NullPointerException("Required nonnull default for " + type.getName() + " does not exist."); 256 } 257 return object; 258 } 259 260 /** 261 * Retrieve the last object of specific type that was registered with 262 * {@link #store(java.lang.Object, java.lang.Class) }. 263 * 264 * Intended for use with i.e. scripts where access to the class type is inconvenient. 265 * In Java code where typing is enforced, use {@link #getDefault(Class)}. 266 * 267 * <p> 268 * Unless specifically set, the default is the last object stored, see the 269 * {@link #setDefault(java.lang.Class, java.lang.Object) } method. 270 * <p> 271 * In some cases, InstanceManager can create the object the first time it's 272 * requested. For more on that, see the class comment. 273 * <p> 274 * In most cases, system configuration assures the existence of a default 275 * object, so this method will log and throw an exception if one doesn't 276 * exist. Use {@link #getNullableDefault(java.lang.Class)} or 277 * {@link #getOptionalDefault(java.lang.Class)} if the default is not 278 * guaranteed to exist. 279 * 280 * @param className Fully qualified class name 281 * @return The default object for type 282 * @throws NullPointerException if no default object for type exists 283 * @throws IllegalArgumentException if the named class doesn't exist 284 * @see #getNullableDefault(java.lang.Class) 285 * @see #getOptionalDefault(java.lang.Class) 286 */ 287 @Nonnull 288 public static Object getDefault(@Nonnull String className) { 289 log.trace("getDefault of type {}", className); 290 Object object = InstanceManager.getNullableDefault(className); 291 if (object == null) { 292 throw new NullPointerException("Required nonnull default for " + className + " does not exist."); 293 } 294 return object; 295 } 296 297 /** 298 * Retrieve the last object of type T that was registered with 299 * {@link #store(java.lang.Object, java.lang.Class) }. 300 * <p> 301 * Unless specifically set, the default is the last object stored, see the 302 * {@link #setDefault(java.lang.Class, java.lang.Object) } method. 303 * <p> 304 * In some cases, InstanceManager can create the object the first time it's 305 * requested. For more on that, see the class comment. 306 * <p> 307 * In most cases, system configuration assures the existence of a default 308 * object, but this method also handles the case where one doesn't exist. 309 * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to 310 * exist. 311 * 312 * @param <T> The type of the class 313 * @param type The class Object for the item's type. 314 * @return The default object for type. 315 * @see #getOptionalDefault(java.lang.Class) 316 */ 317 @CheckForNull 318 public static <T> T getNullableDefault(@Nonnull Class<T> type) { 319 return getDefault().getInstance(type); 320 } 321 322 /** 323 * Retrieve the last object of type T that was registered with 324 * {@link #store(java.lang.Object, java.lang.Class) }. 325 * 326 * Intended for use with i.e. scripts where access to the class type is inconvenient. 327 * In Java code where typing is enforced, use {@link #getNullableDefault(Class)}. 328 * <p> 329 * Unless specifically set, the default is the last object stored, see the 330 * {@link #setDefault(java.lang.Class, java.lang.Object) } method. 331 * <p> 332 * In some cases, InstanceManager can create the object the first time it's 333 * requested. For more on that, see the class comment. 334 * <p> 335 * In most cases, system configuration assures the existence of a default 336 * object, but this method also handles the case where one doesn't exist. 337 * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to 338 * exist. 339 * 340 * @param className Fully qualified class name 341 * @return The default object for type. 342 * @throws IllegalArgumentException if the named class doesn't exist 343 * @see #getOptionalDefault(java.lang.Class) 344 */ 345 @CheckForNull 346 public static Object getNullableDefault(@Nonnull String className) { 347 Class<?> type; 348 try { 349 type = Class.forName(className); 350 } catch (ClassNotFoundException ex) { 351 log.error("No class found: {}", className); 352 throw new IllegalArgumentException(ex); 353 } 354 return getDefault().getInstance(type); 355 } 356 357 /** 358 * Retrieve the last object of type T that was registered with 359 * {@link #store(java.lang.Object, java.lang.Class) }. 360 * <p> 361 * Unless specifically set, the default is the last object stored, see the 362 * {@link #setDefault(java.lang.Class, java.lang.Object) } method. 363 * <p> 364 * In some cases, InstanceManager can create the object the first time it's 365 * requested. For more on that, see the class comment. 366 * <p> 367 * In most cases, system configuration assures the existence of a default 368 * object, but this method also handles the case where one doesn't exist. 369 * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to 370 * exist. 371 * 372 * @param <T> The type of the class 373 * @param type The class Object for the item's type. 374 * @return The default object for type. 375 * @see #getOptionalDefault(java.lang.Class) 376 */ 377 @CheckForNull 378 public <T> T getInstance(@Nonnull Class<T> type) { 379 log.trace("getOptionalDefault of type {}", type.getName()); 380 synchronized (type) { 381 List<T> l = getInstances(type); 382 if (l.isEmpty()) { 383 // example of tracing where something is being initialized 384 log.trace("jmri.implementation.SignalSpeedMap init", new Exception()); 385 if (traceFileActive) { 386 traceFilePrint("Start initialization: " + type.toString()); 387 traceFileIndent++; 388 } 389 390 // check whether already working on this type 391 InitializationState working = getInitializationState(type); 392 Exception except = getInitializationException(type); 393 setInitializationState(type, InitializationState.STARTED); 394 if (working == InitializationState.STARTED) { 395 log.error("Proceeding to initialize {} while already in initialization", type, 396 new Exception("Thread \"" + Thread.currentThread().getName() + "\"")); 397 log.error(" Prior initialization:", except); 398 if (traceFileActive) { 399 traceFilePrint("*** Already in process ***"); 400 } 401 } else if (working == InitializationState.DONE) { 402 log.error("Proceeding to initialize {} but initialization is marked as complete", type, 403 new Exception("Thread \"" + Thread.currentThread().getName() + "\"")); 404 } 405 406 // see if can autocreate 407 log.debug(" attempt auto-create of {}", type.getName()); 408 if (InstanceManagerAutoDefault.class.isAssignableFrom(type)) { 409 try { 410 T obj = type.getConstructor((Class[]) null).newInstance((Object[]) null); 411 l.add(obj); 412 // obj has been added, now initialize it if needed 413 if (obj instanceof InstanceManagerAutoInitialize) { 414 ((InstanceManagerAutoInitialize) obj).initialize(); 415 } 416 log.debug(" auto-created default of {}", type.getName()); 417 } catch ( 418 NoSuchMethodException | 419 InstantiationException | 420 IllegalAccessException | 421 InvocationTargetException e) { 422 log.error("Exception creating auto-default object for {}", type.getName(), e); // unexpected 423 setInitializationState(type, InitializationState.FAILED); 424 if (traceFileActive) { 425 traceFileIndent--; 426 traceFilePrint("End initialization (no object) A: " + type.toString()); 427 } 428 return null; 429 } 430 setInitializationState(type, InitializationState.DONE); 431 if (traceFileActive) { 432 traceFileIndent--; 433 traceFilePrint("End initialization A: " + type.toString()); 434 } 435 return l.get(l.size() - 1); 436 } 437 // see if initializer can handle 438 log.debug(" attempt initializer create of {}", type.getName()); 439 if (initializers.containsKey(type)) { 440 try { 441 @SuppressWarnings("unchecked") 442 T obj = (T) initializers.get(type).getDefault(type); 443 log.debug(" initializer created default of {}", type.getName()); 444 l.add(obj); 445 // obj has been added, now initialize it if needed 446 if (obj instanceof InstanceManagerAutoInitialize) { 447 ((InstanceManagerAutoInitialize) obj).initialize(); 448 } 449 setInitializationState(type, InitializationState.DONE); 450 if (traceFileActive) { 451 traceFileIndent--; 452 traceFilePrint("End initialization I: " + type.toString()); 453 } 454 return l.get(l.size() - 1); 455 } catch (IllegalArgumentException ex) { 456 log.error("Known initializer for {} does not provide a default instance for that class", 457 type.getName()); 458 } 459 } else { 460 log.debug(" no initializer registered for {}", type.getName()); 461 } 462 463 // don't have, can't make 464 setInitializationState(type, InitializationState.FAILED); 465 if (traceFileActive) { 466 traceFileIndent--; 467 traceFilePrint("End initialization (no object) E: " + type.toString()); 468 } 469 return null; 470 } 471 return l.get(l.size() - 1); 472 } 473 } 474 475 /** 476 * Retrieve the last object of type T that was registered with 477 * {@link #store(java.lang.Object, java.lang.Class)} wrapped in an 478 * {@link java.util.Optional}. 479 * <p> 480 * Unless specifically set, the default is the last object stored, see the 481 * {@link #setDefault(java.lang.Class, java.lang.Object)} method. 482 * <p> 483 * In some cases, InstanceManager can create the object the first time it's 484 * requested. For more on that, see the class comment. 485 * <p> 486 * In most cases, system configuration assures the existence of a default 487 * object, but this method also handles the case where one doesn't exist. 488 * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to 489 * exist. 490 * 491 * @param <T> the type of the default class 492 * @param type the class Object for the default type 493 * @return the default wrapped in an Optional or an empty Optional if the 494 * default is null 495 * @see #getNullableDefault(java.lang.Class) 496 */ 497 @Nonnull 498 public static <T> Optional<T> getOptionalDefault(@Nonnull Class< T> type) { 499 return Optional.ofNullable(InstanceManager.getNullableDefault(type)); 500 } 501 502 /** 503 * Set an object of type T as the default for that type. 504 * <p> 505 * Also registers (stores) the object if not already present. 506 * <p> 507 * Now, we do that moving the item to the back of the list; see the 508 * {@link #getDefault} method 509 * 510 * @param <T> The type of the class 511 * @param type The Class object for val 512 * @param item The object to make default for type 513 * @return The default for type (normally this is the item passed in) 514 */ 515 @Nonnull 516 public static <T> T setDefault(@Nonnull Class< T> type, @Nonnull T item) { 517 log.trace("setDefault for type {}", type.getName()); 518 if (item == null) { 519 NullPointerException npe = new NullPointerException(); 520 log.error("Should not set default of type {} to null value", type.getName()); 521 throw npe; 522 } 523 Object oldDefault = containsDefault(type) ? getNullableDefault(type) : null; 524 List<T> l = getList(type); 525 l.remove(item); 526 l.add(item); 527 if (oldDefault == null || !oldDefault.equals(item)) { 528 getDefault().pcs.firePropertyChange(getDefaultsPropertyName(type), oldDefault, item); 529 } 530 return getDefault(type); 531 } 532 533 /** 534 * Check if a default has been set for the given type. 535 * <p> 536 * As a side-effect, then (a) ensures that the list for the given 537 * type exists, though it may be empty, and (b) if it had to create 538 * the list, a PropertyChangeEvent is fired to denote that. 539 * 540 * @param <T> The type of the class 541 * @param type The class type 542 * @return true if an item is available as a default for the given type; 543 * false otherwise 544 */ 545 public static <T> boolean containsDefault(@Nonnull Class<T> type) { 546 List<T> l = getList(type); 547 return !l.isEmpty(); 548 } 549 550 /** 551 * Check if a particular type has been initialized without 552 * triggering an automatic initialization. The existence or 553 * non-existence of the corresponding list is not changed, and 554 * no PropertyChangeEvent is fired. 555 * 556 * @param <T> The type of the class 557 * @param type The class type 558 * @return true if an item is available as a default for the given type; 559 * false otherwise 560 */ 561 public static <T> boolean isInitialized(@Nonnull Class<T> type) { 562 return getDefault().managerLists.get(type) != null; 563 } 564 565 566 /** 567 * Dump generic content of InstanceManager by type. 568 * 569 * @return A formatted multiline list of managed objects 570 */ 571 @Nonnull 572 public static String contentsToString() { 573 574 StringBuilder retval = new StringBuilder(); 575 getDefault().managerLists.keySet().stream().forEachOrdered(c -> { 576 retval.append("List of "); 577 retval.append(c); 578 retval.append(" with "); 579 retval.append(Integer.toString(getList(c).size())); 580 retval.append(" objects\n"); 581 getList(c).stream().forEachOrdered(o -> { 582 retval.append(" "); 583 retval.append(o.getClass().toString()); 584 retval.append("\n"); 585 }); 586 }); 587 return retval.toString(); 588 } 589 590 /** 591 * Get a list of stored types 592 * @return A unmodifiable list of the currently stored types 593 */ 594 public static Set<Class<?>> getInstanceClasses() { 595 return Collections.unmodifiableSet(getDefault().managerLists.keySet()); 596 } 597 598 /** 599 * Remove notification on changes to specific types. 600 * 601 * @param l The listener to remove 602 */ 603 public static synchronized void removePropertyChangeListener(PropertyChangeListener l) { 604 getDefault().pcs.removePropertyChangeListener(l); 605 } 606 607 /** 608 * Remove notification on changes to specific types. 609 * 610 * @param propertyName the property being listened for 611 * @param l The listener to remove 612 */ 613 public static synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener l) { 614 getDefault().pcs.removePropertyChangeListener(propertyName, l); 615 } 616 617 /** 618 * Register for notification on changes to specific types. 619 * 620 * @param l The listener to add 621 */ 622 public static synchronized void addPropertyChangeListener(PropertyChangeListener l) { 623 getDefault().pcs.addPropertyChangeListener(l); 624 } 625 626 /** 627 * Register for notification on changes to specific types 628 * 629 * @param propertyName the property being listened for 630 * @param l The listener to add 631 */ 632 public static synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener l) { 633 getDefault().pcs.addPropertyChangeListener(propertyName, l); 634 } 635 636 /** 637 * Get the property name included in the 638 * {@link java.beans.PropertyChangeEvent} thrown when the default for a 639 * specific class is changed. 640 * 641 * @param clazz the class being listened for 642 * @return the property name 643 */ 644 public static String getDefaultsPropertyName(Class<?> clazz) { 645 return "default-" + clazz.getName(); 646 } 647 648 /** 649 * Get the property name included in the 650 * {@link java.beans.PropertyChangeEvent} thrown when the list for a 651 * specific class is changed. 652 * 653 * @param clazz the class being listened for 654 * @return the property name 655 */ 656 public static String getListPropertyName(Class<?> clazz) { 657 return "list-" + clazz.getName(); 658 } 659 660 /* **************************************************************************** 661 * Primary Accessors - Left (for now) 662 * 663 * These are so extensively used that we're leaving for later 664 * Please don't create any more of these 665 * ****************************************************************************/ 666 /** 667 * May eventually be deprecated, use @{link #getDefault} directly. 668 * 669 * @return the default light manager. May not be the only instance. 670 */ 671 public static LightManager lightManagerInstance() { 672 return getDefault(LightManager.class); 673 } 674 675 /** 676 * May eventually be deprecated, use @{link #getDefault} directly. 677 * 678 * @return the default memory manager. May not be the only instance. 679 */ 680 public static MemoryManager memoryManagerInstance() { 681 return getDefault(MemoryManager.class); 682 } 683 684 /** 685 * May eventually be deprecated, use @{link #getDefault} directly. 686 * 687 * @return the default sensor manager. May not be the only instance. 688 */ 689 public static SensorManager sensorManagerInstance() { 690 return getDefault(SensorManager.class); 691 } 692 693 /** 694 * May eventually be deprecated, use @{link #getDefault} directly. 695 * 696 * @return the default turnout manager. May not be the only instance. 697 */ 698 public static TurnoutManager turnoutManagerInstance() { 699 return getDefault(TurnoutManager.class); 700 } 701 702 /** 703 * May eventually be deprecated, use @{link #getDefault} directly. 704 * 705 * @return the default throttle manager. May not be the only instance. 706 */ 707 public static ThrottleManager throttleManagerInstance() { 708 return getDefault(ThrottleManager.class); 709 } 710 711 /* **************************************************************************** 712 * Old Style Setters - To be migrated 713 * 714 * Migrate away the JMRI uses of these. 715 * ****************************************************************************/ 716 717 // Needs to have proxy manager converted to work 718 // with current list of managers (and robust default 719 // management) before this can be deprecated in favor of 720 // store(p, TurnoutManager.class) 721 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 722 public static void setTurnoutManager(TurnoutManager p) { 723 log.debug(" setTurnoutManager"); 724 TurnoutManager apm = getDefault(TurnoutManager.class); 725 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 726 ((ProxyManager<Turnout>) apm).addManager(p); 727 } else { 728 log.error("Incorrect setup: TurnoutManager default isn't an AbstractProxyManager<Turnout>"); 729 } 730 } 731 732 public static void setThrottleManager(ThrottleManager p) { 733 store(p, ThrottleManager.class); 734 } 735 736 // Needs to have proxy manager converted to work 737 // with current list of managers (and robust default 738 // management) before this can be deprecated in favor of 739 // store(p, TurnoutManager.class) 740 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 741 public static void setLightManager(LightManager p) { 742 log.debug(" setLightManager"); 743 LightManager apm = getDefault(LightManager.class); 744 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 745 ((ProxyManager<Light>) apm).addManager(p); 746 } else { 747 log.error("Incorrect setup: LightManager default isn't an AbstractProxyManager<Light>"); 748 } 749 } 750 751 // Needs to have proxy manager converted to work 752 // with current list of managers (and robust default 753 // management) before this can be deprecated in favor of 754 // store(p, ReporterManager.class) 755 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 756 public static void setReporterManager(ReporterManager p) { 757 log.debug(" setReporterManager"); 758 ReporterManager apm = getDefault(ReporterManager.class); 759 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 760 ((ProxyManager<Reporter>) apm).addManager(p); 761 } else { 762 log.error("Incorrect setup: ReporterManager default isn't an AbstractProxyManager<Reporter>"); 763 } 764 } 765 766 // Needs to have proxy manager converted to work 767 // with current list of managers (and robust default 768 // management) before this can be deprecated in favor of 769 // store(p, SensorManager.class) 770 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 771 public static void setSensorManager(SensorManager p) { 772 log.debug(" setSensorManager"); 773 SensorManager apm = getDefault(SensorManager.class); 774 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 775 ((ProxyManager<Sensor>) apm).addManager(p); 776 } else { 777 log.error("Incorrect setup: SensorManager default isn't an AbstractProxyManager<Sensor>"); 778 } 779 } 780 781 // Needs to have proxy manager converted to work 782 // with current list of managers (and robust default 783 // management) before this can be deprecated in favor of 784 // store(p, IdTagManager.class) 785 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 786 public static void setIdTagManager(IdTagManager p) { 787 log.debug(" setIdTagManager"); 788 IdTagManager apm = getDefault(IdTagManager.class); 789 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 790 ((ProxyManager<IdTag>) apm).addManager(p); 791 } else { 792 log.error("Incorrect setup: IdTagManager default isn't an AbstractProxyManager<IdTag>"); 793 } 794 } 795 796 // Needs to have proxy manager converted to work 797 // with current list of managers (and robust default 798 // management) before this can be deprecated in favor of 799 // store(p, MeterManager.class) 800 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 801 static public void setMeterManager(MeterManager p) { 802 log.debug(" setMeterManager"); 803 MeterManager apm = getDefault(MeterManager.class); 804 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 805 ((ProxyManager<Meter>) apm).addManager(p); 806 } else { 807 log.error("Incorrect setup: MeterManager default isn't an AbstractProxyManager<Meter>"); 808 } 809 } 810 811 // Needs to have proxy manager converted to work 812 // with current list of managers (and robust default 813 // management) before this can be deprecated in favor of 814 // store(p, TurnoutManager.class) 815 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 816 static public void setAnalogIOManager(AnalogIOManager p) { 817 log.debug(" setAnalogIOManager"); 818 AnalogIOManager apm = getDefault(AnalogIOManager.class); 819 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 820 ((ProxyManager<AnalogIO>) apm).addManager(p); 821 } else { 822 log.error("Incorrect setup: AnalogIOManager default isn't an AbstractProxyManager<AnalogIO>"); 823 } 824 } 825 826 // Needs to have proxy manager converted to work 827 // with current list of managers (and robust default 828 // management) before this can be deprecated in favor of 829 // store(p, TurnoutManager.class) 830 @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition 831 static public void setStringIOManager(StringIOManager p) { 832 log.debug(" setStringIOManager"); 833 StringIOManager apm = getDefault(StringIOManager.class); 834 if (apm instanceof ProxyManager<?>) { // <?> due to type erasure 835 ((ProxyManager<StringIO>) apm).addManager(p); 836 } else { 837 log.error("Incorrect setup: StringIOManager default isn't an AbstractProxyManager<StringIO>"); 838 } 839 } 840 841 /* *************************************************************************** */ 842 843 /** 844 * Default constructor for the InstanceManager. 845 */ 846 public InstanceManager() { 847 ServiceLoader.load(InstanceInitializer.class).forEach(provider -> 848 provider.getInitalizes().forEach(cls -> { 849 this.initializers.put(cls, provider); 850 log.debug("Using {} to provide default instance of {}", provider.getClass().getName(), cls.getName()); 851 })); 852 } 853 854 /** 855 * Get a list of all registered objects of type T. 856 * 857 * @param <T> type of the class 858 * @param type class Object for type T 859 * @return a list of registered T instances with the manager or an empty 860 * list 861 */ 862 @SuppressWarnings("unchecked") // the cast here is protected by the structure of the managerLists 863 @Nonnull 864 public <T> List<T> getInstances(@Nonnull Class<T> type) { 865 log.trace("Get list of type {}", type.getName()); 866 synchronized (type) { 867 if (managerLists.get(type) == null) { 868 managerLists.put(type, new ArrayList<>()); 869 pcs.fireIndexedPropertyChange(getListPropertyName(type), 0, null, null); 870 } 871 return (List<T>) managerLists.get(type); 872 } 873 } 874 875 876 /** 877 * Get a list of all registered objects of a specific type. 878 * 879 * Intended for use with i.e. scripts where access to the class type is inconvenient. 880 * 881 * @param <T> type of the class 882 * @param className Fully qualified class name 883 * @return a list of registered instances with the manager or an empty 884 * list 885 * @throws IllegalArgumentException if the named class doesn't exist 886 */ 887 @SuppressWarnings("unchecked") // the cast here is protected by the structure of the managerLists 888 @Nonnull 889 public <T> List<T> getInstances(@Nonnull String className) { 890 Class<?> type; 891 try { 892 type = Class.forName(className); 893 } catch (ClassNotFoundException ex) { 894 log.error("No class found: {}", className); 895 throw new IllegalArgumentException(ex); 896 } 897 log.trace("Get list of type {}", type.getName()); 898 synchronized (type) { 899 if (managerLists.get(type) == null) { 900 managerLists.put(type, new ArrayList<>()); 901 pcs.fireIndexedPropertyChange(getListPropertyName(type), 0, null, null); 902 } 903 return (List<T>) managerLists.get(type); 904 } 905 } 906 907 908 /** 909 * Call {@link jmri.Disposable#dispose()} on the passed in Object if and 910 * only if the passed in Object is not held in any lists. 911 * <p> 912 * Realistically, JMRI can't ensure that all objects and combination of 913 * objects held by the InstanceManager are threadsafe. Therefor dispose() is 914 * called on the Event Dispatch Thread to reduce risk. 915 * 916 * @param disposable the Object to dispose of 917 */ 918 private void dispose(@Nonnull Disposable disposable) { 919 boolean canDispose = true; 920 for (List<?> list : this.managerLists.values()) { 921 if (list.contains(disposable)) { 922 canDispose = false; 923 break; 924 } 925 } 926 if (canDispose) { 927 ThreadingUtil.runOnGUI(disposable::dispose); 928 } 929 } 930 931 /** 932 * Clear all managed instances from the common instance manager, effectively 933 * installing a new one. 934 */ 935 public void clearAll() { 936 log.debug("Clearing InstanceManager"); 937 if (traceFileActive) traceFileWriter.println("clearAll"); 938 939 // reset the instance manager, so future calls will invoke the new one 940 LazyInstanceManager.resetInstanceManager(); 941 942 // continue to clean up this one 943 new HashSet<>(managerLists.keySet()).forEach(this::clear); 944 managerLists.keySet().forEach(type -> { 945 if (getInitializationState(type) != InitializationState.NOTSET) { 946 log.warn("list of {} was reinitialized during clearAll", type, new Exception()); 947 if (traceFileActive) traceFileWriter.println("WARN: list of "+type+" was reinitialized during clearAll"); 948 } 949 if (!managerLists.get(type).isEmpty()) { 950 log.warn("list of {} was not cleared, {} entries", type, managerLists.get(type).size(), new Exception()); 951 if (traceFileActive) traceFileWriter.println("WARN: list of "+type+" was not cleared, "+managerLists.get(type).size()+" entries"); 952 } 953 }); 954 if (traceFileActive) { 955 traceFileWriter.println(""); // marks new InstanceManager 956 traceFileWriter.flush(); 957 } 958 } 959 960 /** 961 * Clear all managed instances of a particular type from this 962 * InstanceManager. 963 * 964 * @param <T> the type of class to clear 965 * @param type the type to clear 966 */ 967 public <T> void clear(@Nonnull Class<T> type) { 968 log.trace("Clearing managers of {}", type.getName()); 969 List<T> toClear = new ArrayList<>(getInstances(type)); 970 toClear.forEach(o -> remove(o, type)); 971 setInitializationState(type, InitializationState.NOTSET); // initialization will have to be redone 972 managerLists.put(type, new ArrayList<>()); 973 } 974 975 /** 976 * A class for lazy initialization of the singleton class InstanceManager. 977 * 978 * See https://www.ibm.com/developerworks/library/j-jtp03304/ 979 */ 980 private static class LazyInstanceManager { 981 982 private static InstanceManager instanceManager = new InstanceManager(); 983 984 /** 985 * Get the InstanceManager. 986 */ 987 public static InstanceManager getInstanceManager() { 988 return instanceManager; 989 } 990 991 /** 992 * Replace the (static) InstanceManager. 993 */ 994 public static synchronized void resetInstanceManager() { 995 try { 996 instanceManager = new InstanceManager(); 997 } catch (Exception e) { 998 log.error("can't create new InstanceManager"); 999 } 1000 } 1001 1002 } 1003 1004 /** 1005 * Get the default instance of the InstanceManager. This is used for 1006 * verifying the source of events fired by the InstanceManager. 1007 * 1008 * @return the default instance of the InstanceManager, creating it if 1009 * needed 1010 */ 1011 @Nonnull 1012 public static InstanceManager getDefault() { 1013 return LazyInstanceManager.getInstanceManager(); 1014 } 1015 1016 // support checking for overlapping intialization 1017 private enum InitializationState { 1018 NOTSET, // synonymous with no value for this stored 1019 NOTSTARTED, 1020 STARTED, 1021 FAILED, 1022 DONE 1023 } 1024 1025 private static final class StateHolder { 1026 1027 InitializationState state; 1028 Exception exception; 1029 1030 StateHolder(InitializationState state, Exception exception) { 1031 this.state = state; 1032 this.exception = exception; 1033 } 1034 } 1035 1036 private void setInitializationState(Class<?> type, InitializationState state) { 1037 log.trace("set state {} for {}", type, state); 1038 if (state == InitializationState.STARTED) { 1039 initState.put(type, new StateHolder(state, new Exception("Thread " + Thread.currentThread().getName()))); 1040 } else { 1041 initState.put(type, new StateHolder(state, null)); 1042 } 1043 } 1044 1045 private InitializationState getInitializationState(Class<?> type) { 1046 StateHolder holder = initState.get(type); 1047 if (holder == null) { 1048 return InitializationState.NOTSET; 1049 } 1050 return holder.state; 1051 } 1052 1053 private Exception getInitializationException(Class<?> type) { 1054 StateHolder holder = initState.get(type); 1055 if (holder == null) { 1056 return null; 1057 } 1058 return holder.exception; 1059 } 1060 1061 private static final Logger log = LoggerFactory.getLogger(InstanceManager.class); 1062 1063 // support creating a file with initialization summary information 1064 private static final boolean traceFileActive = log.isTraceEnabled(); // or manually force true 1065 private static final boolean traceFileAppend = false; // append from run to run 1066 private int traceFileIndent = 1; // used to track overlap, but note that threads are parallel 1067 private static final String traceFileName = "instanceManagerSequence.txt"; // use a standalone name 1068 private static PrintWriter traceFileWriter; 1069 1070 static { 1071 PrintWriter tempWriter = null; 1072 try { 1073 tempWriter = (traceFileActive 1074 ? new PrintWriter(new BufferedWriter(new FileWriter(new File(traceFileName), traceFileAppend))) 1075 : null); 1076 } catch (java.io.IOException e) { 1077 log.error("failed to open log file", e); 1078 } finally { 1079 traceFileWriter = tempWriter; 1080 } 1081 } 1082 1083 private void traceFilePrint(String msg) { 1084 String pad = org.apache.commons.lang3.StringUtils.repeat(' ', traceFileIndent * 2); 1085 String threadName = "[" + Thread.currentThread().getName() + "]"; 1086 String threadNamePad = org.apache.commons.lang3.StringUtils.repeat(' ', Math.max(25 - threadName.length(), 0)); 1087 String text = threadName + threadNamePad + "|" + pad + msg; 1088 traceFileWriter.println(text); 1089 traceFileWriter.flush(); 1090 log.trace(text); 1091 } 1092 1093}