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     * Retrieve a list of all objects of type T that were registered with
109     * {@link #store}.
110     *
111     * @param <T>  The type of the class
112     * @param type The class Object for the items' type.
113     * @return A list of type Objects registered with the manager or an empty
114     *         list.
115     */
116    @Nonnull
117    public static <T> List<T> getList(@Nonnull Class<T> type) {
118        return getDefault().getInstances(type);
119    }
120
121    /**
122     * Deregister all objects of a particular type.
123     *
124     * @param <T>  The type of the class
125     * @param type The class Object for the items to be removed.
126     */
127    public static <T> void reset(@Nonnull Class<T> type) {
128        getDefault().clear(type);
129    }
130
131    /**
132     * Remove an object of a particular type that had earlier been registered
133     * with {@link #store}. If item was previously registered, this will remove
134     * item and fire an indexed property change event for the property matching
135     * the output of {@link #getListPropertyName(java.lang.Class)} for type.
136     * <p>
137     * This is the static access to
138     * {@link #remove(java.lang.Object, java.lang.Class)}.
139     *
140     * @param <T>  The type of the class
141     * @param item The object of type T to be deregistered
142     * @param type The class Object for the item's type
143     */
144    public static <T> void deregister(@Nonnull T item, @Nonnull Class<T> type) {
145        getDefault().remove(item, type);
146    }
147
148    /**
149     * Remove an object of a particular type that had earlier been registered
150     * with {@link #store}. If item was previously registered, this will remove
151     * item and fire an indexed property change event for the property matching
152     * the output of {@link #getListPropertyName(java.lang.Class)} for type.
153     *
154     * @param <T>  The type of the class
155     * @param item The object of type T to be deregistered
156     * @param type The class Object for the item's type
157     */
158    public <T> void remove(@Nonnull T item, @Nonnull Class<T> type) {
159        log.debug("Remove item type {}", type.getName());
160        List<T> l = getList(type);
161        int index = l.indexOf(item);
162        if (index != -1) { // -1 means items was not in list, and therefor, not registered
163            l.remove(item);
164            if (item instanceof Disposable) {
165                dispose((Disposable) item);
166            }
167        }
168        // if removing last item, re-initialize later
169        if (l.isEmpty()) {
170            setInitializationState(type, InitializationState.NOTSET);
171        }
172        if (index != -1) { // -1 means items was not in list, and therefor, not registered
173            // fire property change last
174            pcs.fireIndexedPropertyChange(getListPropertyName(type), index, item, null);
175        }
176    }
177
178    /**
179     * Retrieve the last object of type T that was registered with
180     * {@link #store(java.lang.Object, java.lang.Class) }.
181     * <p>
182     * Unless specifically set, the default is the last object stored, see the
183     * {@link #setDefault(java.lang.Class, java.lang.Object) } method.
184     * <p>
185     * In some cases, InstanceManager can create the object the first time it's
186     * requested. For more on that, see the class comment.
187     * <p>
188     * In most cases, system configuration assures the existence of a default
189     * object, so this method will log and throw an exception if one doesn't
190     * exist. Use {@link #getNullableDefault(java.lang.Class)} or
191     * {@link #getOptionalDefault(java.lang.Class)} if the default is not
192     * guaranteed to exist.
193     *
194     * @param <T>  The type of the class
195     * @param type The class Object for the item's type
196     * @return The default object for type
197     * @throws NullPointerException if no default object for type exists
198     * @see #getNullableDefault(java.lang.Class)
199     * @see #getOptionalDefault(java.lang.Class)
200     */
201    @Nonnull
202    public static <T> T getDefault(@Nonnull Class<T> type) {
203        log.trace("getDefault of type {}", type.getName());
204        T object = InstanceManager.getNullableDefault(type);
205        if (object == null) {
206            throw new NullPointerException("Required nonnull default for " + type.getName() + " does not exist.");
207        }
208        return object;
209    }
210
211    /**
212     * Retrieve the last object of type T that was registered with
213     * {@link #store(java.lang.Object, java.lang.Class) }.
214     * <p>
215     * Unless specifically set, the default is the last object stored, see the
216     * {@link #setDefault(java.lang.Class, java.lang.Object) } method.
217     * <p>
218     * In some cases, InstanceManager can create the object the first time it's
219     * requested. For more on that, see the class comment.
220     * <p>
221     * In most cases, system configuration assures the existence of a default
222     * object, but this method also handles the case where one doesn't exist.
223     * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to
224     * exist.
225     *
226     * @param <T>  The type of the class
227     * @param type The class Object for the item's type.
228     * @return The default object for type.
229     * @see #getOptionalDefault(java.lang.Class)
230     */
231    @CheckForNull
232    public static <T> T getNullableDefault(@Nonnull Class<T> type) {
233        return getDefault().getInstance(type);
234    }
235
236    /**
237     * Retrieve the last object of type T that was registered with
238     * {@link #store(java.lang.Object, java.lang.Class) }.
239     * <p>
240     * Unless specifically set, the default is the last object stored, see the
241     * {@link #setDefault(java.lang.Class, java.lang.Object) } method.
242     * <p>
243     * In some cases, InstanceManager can create the object the first time it's
244     * requested. For more on that, see the class comment.
245     * <p>
246     * In most cases, system configuration assures the existence of a default
247     * object, but this method also handles the case where one doesn't exist.
248     * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to
249     * exist.
250     *
251     * @param <T>  The type of the class
252     * @param type The class Object for the item's type.
253     * @return The default object for type.
254     * @see #getOptionalDefault(java.lang.Class)
255     */
256    @CheckForNull
257    public <T> T getInstance(@Nonnull Class<T> type) {
258        log.trace("getOptionalDefault of type {}", type.getName());
259        synchronized (type) {
260            List<T> l = getInstances(type);
261            if (l.isEmpty()) {
262                // example of tracing where something is being initialized
263                log.trace("jmri.implementation.SignalSpeedMap init", new Exception());
264                if (traceFileActive) {
265                    traceFilePrint("Start initialization: " + type.toString());
266                    traceFileIndent++;
267                }
268
269                // check whether already working on this type
270                InitializationState working = getInitializationState(type);
271                Exception except = getInitializationException(type);
272                setInitializationState(type, InitializationState.STARTED);
273                if (working == InitializationState.STARTED) {
274                    log.error("Proceeding to initialize {} while already in initialization", type,
275                            new Exception("Thread \"" + Thread.currentThread().getName() + "\""));
276                    log.error("    Prior initialization:", except);
277                    if (traceFileActive) {
278                        traceFilePrint("*** Already in process ***");
279                    }
280                } else if (working == InitializationState.DONE) {
281                    log.error("Proceeding to initialize {} but initialization is marked as complete", type,
282                            new Exception("Thread \"" + Thread.currentThread().getName() + "\""));
283                }
284
285                // see if can autocreate
286                log.debug("    attempt auto-create of {}", type.getName());
287                if (InstanceManagerAutoDefault.class.isAssignableFrom(type)) {
288                    try {
289                        T obj = type.getConstructor((Class[]) null).newInstance((Object[]) null);
290                        l.add(obj);
291                        // obj has been added, now initialize it if needed
292                        if (obj instanceof InstanceManagerAutoInitialize) {
293                            ((InstanceManagerAutoInitialize) obj).initialize();
294                        }
295                        log.debug("      auto-created default of {}", type.getName());
296                    } catch (
297                            NoSuchMethodException |
298                            InstantiationException |
299                            IllegalAccessException |
300                            InvocationTargetException e) {
301                        log.error("Exception creating auto-default object for {}", type.getName(), e); // unexpected
302                        setInitializationState(type, InitializationState.FAILED);
303                        if (traceFileActive) {
304                            traceFileIndent--;
305                            traceFilePrint("End initialization (no object) A: " + type.toString());
306                        }
307                        return null;
308                    }
309                    setInitializationState(type, InitializationState.DONE);
310                    if (traceFileActive) {
311                        traceFileIndent--;
312                        traceFilePrint("End initialization A: " + type.toString());
313                    }
314                    return l.get(l.size() - 1);
315                }
316                // see if initializer can handle
317                log.debug("    attempt initializer create of {}", type.getName());
318                if (initializers.containsKey(type)) {
319                    try {
320                        @SuppressWarnings("unchecked")
321                        T obj = (T) initializers.get(type).getDefault(type);
322                        log.debug("      initializer created default of {}", type.getName());
323                        l.add(obj);
324                        // obj has been added, now initialize it if needed
325                        if (obj instanceof InstanceManagerAutoInitialize) {
326                            ((InstanceManagerAutoInitialize) obj).initialize();
327                        }
328                        setInitializationState(type, InitializationState.DONE);
329                        if (traceFileActive) {
330                            traceFileIndent--;
331                            traceFilePrint("End initialization I: " + type.toString());
332                        }
333                        return l.get(l.size() - 1);
334                    } catch (IllegalArgumentException ex) {
335                        log.error("Known initializer for {} does not provide a default instance for that class",
336                                type.getName());
337                    }
338                } else {
339                    log.debug("        no initializer registered for {}", type.getName());
340                }
341
342                // don't have, can't make
343                setInitializationState(type, InitializationState.FAILED);
344                if (traceFileActive) {
345                    traceFileIndent--;
346                    traceFilePrint("End initialization (no object) E: " + type.toString());
347                }
348                return null;
349            }
350            return l.get(l.size() - 1);
351        }
352    }
353
354    /**
355     * Retrieve the last object of type T that was registered with
356     * {@link #store(java.lang.Object, java.lang.Class)} wrapped in an
357     * {@link java.util.Optional}.
358     * <p>
359     * Unless specifically set, the default is the last object stored, see the
360     * {@link #setDefault(java.lang.Class, java.lang.Object)} method.
361     * <p>
362     * In some cases, InstanceManager can create the object the first time it's
363     * requested. For more on that, see the class comment.
364     * <p>
365     * In most cases, system configuration assures the existence of a default
366     * object, but this method also handles the case where one doesn't exist.
367     * Use {@link #getDefault(java.lang.Class)} when the object is guaranteed to
368     * exist.
369     *
370     * @param <T>  the type of the default class
371     * @param type the class Object for the default type
372     * @return the default wrapped in an Optional or an empty Optional if the
373     *         default is null
374     * @see #getNullableDefault(java.lang.Class)
375     */
376    @Nonnull
377    public static <T> Optional<T> getOptionalDefault(@Nonnull Class< T> type) {
378        return Optional.ofNullable(InstanceManager.getNullableDefault(type));
379    }
380
381    /**
382     * Set an object of type T as the default for that type.
383     * <p>
384     * Also registers (stores) the object if not already present.
385     * <p>
386     * Now, we do that moving the item to the back of the list; see the
387     * {@link #getDefault} method
388     *
389     * @param <T>  The type of the class
390     * @param type The Class object for val
391     * @param item The object to make default for type
392     * @return The default for type (normally this is the item passed in)
393     */
394    @Nonnull
395    public static <T> T setDefault(@Nonnull Class< T> type, @Nonnull T item) {
396        log.trace("setDefault for type {}", type.getName());
397        if (item == null) {
398            NullPointerException npe = new NullPointerException();
399            log.error("Should not set default of type {} to null value", type.getName());
400            throw npe;
401        }
402        Object oldDefault = containsDefault(type) ? getNullableDefault(type) : null;
403        List<T> l = getList(type);
404        l.remove(item);
405        l.add(item);
406        if (oldDefault == null || !oldDefault.equals(item)) {
407            getDefault().pcs.firePropertyChange(getDefaultsPropertyName(type), oldDefault, item);
408        }
409        return getDefault(type);
410    }
411
412    /**
413     * Check if a default has been set for the given type.
414     * <p>
415     * As a side-effect, then (a) ensures that the list for the given 
416     * type exists, though it may be empty, and (b) if it had to create
417     * the list, a PropertyChangeEvent is fired to denote that.
418     *
419     * @param <T>  The type of the class
420     * @param type The class type
421     * @return true if an item is available as a default for the given type;
422     *         false otherwise
423     */
424    public static <T> boolean containsDefault(@Nonnull Class<T> type) {
425        List<T> l = getList(type);
426        return !l.isEmpty();
427    }
428
429    /**
430     * Check if a particular type has been initialized without
431     * triggering an automatic initialization. The existence or
432     * non-existence of the corresponding list is not changed, and 
433     * no PropertyChangeEvent is fired.
434     *
435     * @param <T>  The type of the class
436     * @param type The class type
437     * @return true if an item is available as a default for the given type;
438     *         false otherwise
439     */
440    public static <T> boolean isInitialized(@Nonnull Class<T> type) {
441        return getDefault().managerLists.get(type) != null;
442    }
443
444
445    /**
446     * Dump generic content of InstanceManager by type.
447     *
448     * @return A formatted multiline list of managed objects
449     */
450    @Nonnull
451    public static String contentsToString() {
452
453        StringBuilder retval = new StringBuilder();
454        getDefault().managerLists.keySet().stream().forEachOrdered(c -> {
455            retval.append("List of ");
456            retval.append(c);
457            retval.append(" with ");
458            retval.append(Integer.toString(getList(c).size()));
459            retval.append(" objects\n");
460            getList(c).stream().forEachOrdered(o -> {
461                retval.append("    ");
462                retval.append(o.getClass().toString());
463                retval.append("\n");
464            });
465        });
466        return retval.toString();
467    }
468
469    /**
470     * Remove notification on changes to specific types.
471     *
472     * @param l The listener to remove
473     */
474    public static synchronized void removePropertyChangeListener(PropertyChangeListener l) {
475        getDefault().pcs.removePropertyChangeListener(l);
476    }
477
478    /**
479     * Remove notification on changes to specific types.
480     *
481     * @param propertyName the property being listened for
482     * @param l            The listener to remove
483     */
484    public static synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
485        getDefault().pcs.removePropertyChangeListener(propertyName, l);
486    }
487
488    /**
489     * Register for notification on changes to specific types.
490     *
491     * @param l The listener to add
492     */
493    public static synchronized void addPropertyChangeListener(PropertyChangeListener l) {
494        getDefault().pcs.addPropertyChangeListener(l);
495    }
496
497    /**
498     * Register for notification on changes to specific types
499     *
500     * @param propertyName the property being listened for
501     * @param l            The listener to add
502     */
503    public static synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
504        getDefault().pcs.addPropertyChangeListener(propertyName, l);
505    }
506
507    /**
508     * Get the property name included in the
509     * {@link java.beans.PropertyChangeEvent} thrown when the default for a
510     * specific class is changed.
511     *
512     * @param clazz the class being listened for
513     * @return the property name
514     */
515    public static String getDefaultsPropertyName(Class<?> clazz) {
516        return "default-" + clazz.getName();
517    }
518
519    /**
520     * Get the property name included in the
521     * {@link java.beans.PropertyChangeEvent} thrown when the list for a
522     * specific class is changed.
523     *
524     * @param clazz the class being listened for
525     * @return the property name
526     */
527    public static String getListPropertyName(Class<?> clazz) {
528        return "list-" + clazz.getName();
529    }
530
531    /* ****************************************************************************
532     *                   Primary Accessors - Left (for now)
533     *
534     *          These are so extensively used that we're leaving for later
535     *                      Please don't create any more of these
536     * ****************************************************************************/
537    /**
538     * May eventually be deprecated, use @{link #getDefault} directly.
539     *
540     * @return the default light manager. May not be the only instance.
541     */
542    public static LightManager lightManagerInstance() {
543        return getDefault(LightManager.class);
544    }
545
546    /**
547     * May eventually be deprecated, use @{link #getDefault} directly.
548     *
549     * @return the default memory manager. May not be the only instance.
550     */
551    public static MemoryManager memoryManagerInstance() {
552        return getDefault(MemoryManager.class);
553    }
554
555    /**
556     * May eventually be deprecated, use @{link #getDefault} directly.
557     *
558     * @return the default sensor manager. May not be the only instance.
559     */
560    public static SensorManager sensorManagerInstance() {
561        return getDefault(SensorManager.class);
562    }
563
564    /**
565     * May eventually be deprecated, use @{link #getDefault} directly.
566     *
567     * @return the default turnout manager. May not be the only instance.
568     */
569    public static TurnoutManager turnoutManagerInstance() {
570        return getDefault(TurnoutManager.class);
571    }
572
573    /**
574     * May eventually be deprecated, use @{link #getDefault} directly.
575     *
576     * @return the default throttle manager. May not be the only instance.
577     */
578    public static ThrottleManager throttleManagerInstance() {
579        return getDefault(ThrottleManager.class);
580    }
581
582    /* ****************************************************************************
583     *                   Old Style Setters - To be migrated
584     *
585     *                   Migrate away the JMRI uses of these.
586     * ****************************************************************************/
587
588    // Needs to have proxy manager converted to work
589    // with current list of managers (and robust default
590    // management) before this can be deprecated in favor of
591    // store(p, TurnoutManager.class)
592    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
593    public static void setTurnoutManager(TurnoutManager p) {
594        log.debug(" setTurnoutManager");
595        TurnoutManager apm = getDefault(TurnoutManager.class);
596        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
597            ((ProxyManager<Turnout>) apm).addManager(p);
598        } else {
599            log.error("Incorrect setup: TurnoutManager default isn't an AbstractProxyManager<Turnout>");
600        }
601    }
602
603    public static void setThrottleManager(ThrottleManager p) {
604        store(p, ThrottleManager.class);
605    }
606
607    // Needs to have proxy manager converted to work
608    // with current list of managers (and robust default
609    // management) before this can be deprecated in favor of
610    // store(p, TurnoutManager.class)
611    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
612    public static void setLightManager(LightManager p) {
613        log.debug(" setLightManager");
614        LightManager apm = getDefault(LightManager.class);
615        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
616            ((ProxyManager<Light>) apm).addManager(p);
617        } else {
618            log.error("Incorrect setup: LightManager default isn't an AbstractProxyManager<Light>");
619        }
620    }
621
622    // Needs to have proxy manager converted to work
623    // with current list of managers (and robust default
624    // management) before this can be deprecated in favor of
625    // store(p, ReporterManager.class)
626    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
627    public static void setReporterManager(ReporterManager p) {
628        log.debug(" setReporterManager");
629        ReporterManager apm = getDefault(ReporterManager.class);
630        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
631            ((ProxyManager<Reporter>) apm).addManager(p);
632        } else {
633            log.error("Incorrect setup: ReporterManager default isn't an AbstractProxyManager<Reporter>");
634        }
635    }
636
637    // Needs to have proxy manager converted to work
638    // with current list of managers (and robust default
639    // management) before this can be deprecated in favor of
640    // store(p, SensorManager.class)
641    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
642    public static void setSensorManager(SensorManager p) {
643        log.debug(" setSensorManager");
644        SensorManager apm = getDefault(SensorManager.class);
645        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
646            ((ProxyManager<Sensor>) apm).addManager(p);
647        } else {
648            log.error("Incorrect setup: SensorManager default isn't an AbstractProxyManager<Sensor>");
649        }
650    }
651
652    // Needs to have proxy manager converted to work
653    // with current list of managers (and robust default
654    // management) before this can be deprecated in favor of
655    // store(p, IdTagManager.class)
656    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
657    public static void setIdTagManager(IdTagManager p) {
658        log.debug(" setIdTagManager");
659        IdTagManager apm = getDefault(IdTagManager.class);
660        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
661            ((ProxyManager<IdTag>) apm).addManager(p);
662        } else {
663            log.error("Incorrect setup: IdTagManager default isn't an AbstractProxyManager<IdTag>");
664        }
665    }
666
667    // Needs to have proxy manager converted to work
668    // with current list of managers (and robust default
669    // management) before this can be deprecated in favor of
670    // store(p, MeterManager.class)
671    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
672    static public void setMeterManager(MeterManager p) {
673        log.debug(" setMeterManager");
674        MeterManager apm = getDefault(MeterManager.class);
675        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
676            ((ProxyManager<Meter>) apm).addManager(p);
677        } else {
678            log.error("Incorrect setup: MeterManager default isn't an AbstractProxyManager<Meter>");
679        }
680    }
681
682    // Needs to have proxy manager converted to work
683    // with current list of managers (and robust default
684    // management) before this can be deprecated in favor of
685    // store(p, TurnoutManager.class)
686    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
687    static public void setAnalogIOManager(AnalogIOManager p) {
688        log.debug(" setAnalogIOManager");
689        AnalogIOManager apm = getDefault(AnalogIOManager.class);
690        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
691            ((ProxyManager<AnalogIO>) apm).addManager(p);
692        } else {
693            log.error("Incorrect setup: AnalogIOManager default isn't an AbstractProxyManager<AnalogIO>");
694        }
695    }
696
697    // Needs to have proxy manager converted to work
698    // with current list of managers (and robust default
699    // management) before this can be deprecated in favor of
700    // store(p, TurnoutManager.class)
701    @SuppressWarnings("unchecked") // AbstractProxyManager of the right type is type-safe by definition
702    static public void setStringIOManager(StringIOManager p) {
703        log.debug(" setStringIOManager");
704        StringIOManager apm = getDefault(StringIOManager.class);
705        if (apm instanceof ProxyManager<?>) { // <?> due to type erasure
706            ((ProxyManager<StringIO>) apm).addManager(p);
707        } else {
708            log.error("Incorrect setup: StringIOManager default isn't an AbstractProxyManager<StringIO>");
709        }
710    }
711
712    /* *************************************************************************** */
713
714    /**
715     * Default constructor for the InstanceManager.
716     */
717    public InstanceManager() {
718        ServiceLoader.load(InstanceInitializer.class).forEach(provider ->
719            provider.getInitalizes().forEach(cls -> {
720                this.initializers.put(cls, provider);
721                log.debug("Using {} to provide default instance of {}", provider.getClass().getName(), cls.getName());
722            }));
723    }
724
725    /**
726     * Get a list of all registered objects of type T.
727     *
728     * @param <T>  type of the class
729     * @param type class Object for type T
730     * @return a list of registered T instances with the manager or an empty
731     *         list
732     */
733    @SuppressWarnings("unchecked") // the cast here is protected by the structure of the managerLists
734    @Nonnull
735    public <T> List<T> getInstances(@Nonnull Class<T> type) {
736        log.trace("Get list of type {}", type.getName());
737        synchronized (type) {
738            if (managerLists.get(type) == null) {
739                managerLists.put(type, new ArrayList<>());
740                pcs.fireIndexedPropertyChange(getListPropertyName(type), 0, null, null);
741            }
742            return (List<T>) managerLists.get(type);
743        }
744    }
745
746    /**
747     * Call {@link jmri.Disposable#dispose()} on the passed in Object if and
748     * only if the passed in Object is not held in any lists.
749     * <p>
750     * Realistically, JMRI can't ensure that all objects and combination of
751     * objects held by the InstanceManager are threadsafe. Therefor dispose() is
752     * called on the Event Dispatch Thread to reduce risk.
753     *
754     * @param disposable the Object to dispose of
755     */
756    private void dispose(@Nonnull Disposable disposable) {
757        boolean canDispose = true;
758        for (List<?> list : this.managerLists.values()) {
759            if (list.contains(disposable)) {
760                canDispose = false;
761                break;
762            }
763        }
764        if (canDispose) {
765            ThreadingUtil.runOnGUI(disposable::dispose);
766        }
767    }
768
769    /**
770     * Clear all managed instances from the common instance manager, effectively
771     * installing a new one.
772     */
773    public void clearAll() {
774        log.debug("Clearing InstanceManager");
775        if (traceFileActive) traceFileWriter.println("clearAll");
776        
777        // reset the instance manager, so future calls will invoke the new one
778        LazyInstanceManager.resetInstanceManager();
779        
780        // continue to clean up this one
781        new HashSet<>(managerLists.keySet()).forEach(this::clear);
782        managerLists.keySet().forEach(type -> {
783            if (getInitializationState(type) != InitializationState.NOTSET) {
784                log.warn("list of {} was reinitialized during clearAll", type, new Exception());
785                if (traceFileActive) traceFileWriter.println("WARN: list of "+type+" was reinitialized during clearAll");
786            }
787            if (!managerLists.get(type).isEmpty()) {
788                log.warn("list of {} was not cleared, {} entries", type, managerLists.get(type).size(), new Exception());
789                if (traceFileActive) traceFileWriter.println("WARN: list of "+type+" was not cleared, "+managerLists.get(type).size()+" entries");
790            }
791        });
792        if (traceFileActive) {
793            traceFileWriter.println(""); // marks new InstanceManager
794            traceFileWriter.flush();
795        }
796    }
797
798    /**
799     * Clear all managed instances of a particular type from this
800     * InstanceManager.
801     *
802     * @param <T>  the type of class to clear
803     * @param type the type to clear
804     */
805    public <T> void clear(@Nonnull Class<T> type) {
806        log.trace("Clearing managers of {}", type.getName());
807        List<T> toClear = new ArrayList<>(getInstances(type));
808        toClear.forEach(o -> remove(o, type));
809        setInitializationState(type, InitializationState.NOTSET); // initialization will have to be redone
810        managerLists.put(type, new ArrayList<>());
811    }
812
813    /**
814     * A class for lazy initialization of the singleton class InstanceManager.
815     *
816     * See https://www.ibm.com/developerworks/library/j-jtp03304/
817     */
818    private static class LazyInstanceManager {
819
820        private static InstanceManager instanceManager = new InstanceManager();
821
822        /**
823         * Get the InstanceManager.
824         */
825        public static InstanceManager getInstanceManager() {
826            return instanceManager;
827        }
828
829        /**
830         * Replace the (static) InstanceManager.
831         */
832        public static synchronized void resetInstanceManager() {
833            try {
834                instanceManager = new InstanceManager();
835            } catch (Exception e) {
836                log.error("can't create new InstanceManager");
837            }
838        }
839
840    }
841
842    /**
843     * Get the default instance of the InstanceManager. This is used for
844     * verifying the source of events fired by the InstanceManager.
845     *
846     * @return the default instance of the InstanceManager, creating it if
847     *         needed
848     */
849    @Nonnull
850    public static InstanceManager getDefault() {
851        return LazyInstanceManager.getInstanceManager();
852    }
853
854    // support checking for overlapping intialization
855    private enum InitializationState {
856        NOTSET, // synonymous with no value for this stored
857        NOTSTARTED,
858        STARTED,
859        FAILED,
860        DONE
861    }
862
863    private static final class StateHolder {
864
865        InitializationState state;
866        Exception exception;
867
868        StateHolder(InitializationState state, Exception exception) {
869            this.state = state;
870            this.exception = exception;
871        }
872    }
873
874    private void setInitializationState(Class<?> type, InitializationState state) {
875        log.trace("set state {} for {}", type, state);
876        if (state == InitializationState.STARTED) {
877            initState.put(type, new StateHolder(state, new Exception("Thread " + Thread.currentThread().getName())));
878        } else {
879            initState.put(type, new StateHolder(state, null));
880        }
881    }
882
883    private InitializationState getInitializationState(Class<?> type) {
884        StateHolder holder = initState.get(type);
885        if (holder == null) {
886            return InitializationState.NOTSET;
887        }
888        return holder.state;
889    }
890
891    private Exception getInitializationException(Class<?> type) {
892        StateHolder holder = initState.get(type);
893        if (holder == null) {
894            return null;
895        }
896        return holder.exception;
897    }
898
899    private static final Logger log = LoggerFactory.getLogger(InstanceManager.class);
900
901    // support creating a file with initialization summary information
902    private static final boolean traceFileActive = log.isTraceEnabled(); // or manually force true
903    private static final boolean traceFileAppend = false; // append from run to run
904    private int traceFileIndent = 1; // used to track overlap, but note that threads are parallel
905    private static final String traceFileName = "instanceManagerSequence.txt";  // use a standalone name
906    private static PrintWriter traceFileWriter;
907
908    static {
909        PrintWriter tempWriter = null;
910        try {
911            tempWriter = (traceFileActive
912                    ? new PrintWriter(new BufferedWriter(new FileWriter(new File(traceFileName), traceFileAppend)))
913                    : null);
914        } catch (java.io.IOException e) {
915            log.error("failed to open log file", e);
916        } finally {
917            traceFileWriter = tempWriter;
918        }
919    }
920
921    private void traceFilePrint(String msg) {
922        String pad = org.apache.commons.lang3.StringUtils.repeat(' ', traceFileIndent * 2);
923        String threadName = "[" + Thread.currentThread().getName() + "]";
924        String threadNamePad = org.apache.commons.lang3.StringUtils.repeat(' ', Math.max(25 - threadName.length(), 0));
925        String text = threadName + threadNamePad + "|" + pad + msg;
926        traceFileWriter.println(text);
927        traceFileWriter.flush();
928        log.trace(text);
929    }
930
931}