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}