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