001package jmri.jmrit.beantable.signalmast;
002
003
004import java.util.*;
005
006import javax.annotation.Nonnull;
007import javax.swing.*;
008
009import jmri.*;
010import jmri.spi.JmriServiceProviderInterface;
011
012/**
013 * Definition of JPanel used to configure a specific SignalMast type.
014 *
015 * Implementing classes <em>must</em> be registered as service providers of this
016 * type to be recognized and usable.
017 * <p>
018 * General design documentation is available on the 
019 * <a href="http://jmri.org/help/en/html/doc/Technical/SystemStructure.shtml">Structure of
020 * External System Connections page</a>.
021 *
022 * The general sequence is:
023 * <ul>
024 * <li>Find one or more object of this type that have {@link SignalMastAddPaneProvider#isAvailable} true.
025 * <li>Invoke {@link #setAspectNames} from the selected signal system
026 * <li>If you're showing a mast that exists, invoke {@link #setMast} to load the contents
027 * <li>To eventually create or update a mast from the entered data, invoke {@link #createMast}
028 * </ul>
029 * 
030 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2018
031 * @see java.util.ServiceLoader
032 * @see AddSignalMastPanel
033 * @since 4.11.3
034 */
035public abstract class SignalMastAddPane extends JPanel implements JmriServiceProviderInterface {
036
037    /**
038     * Provide a new list of aspects in the signal system.
039     * Must be done at startup before the pane is shown.
040     * May be done later, to update to a newly selected system.
041     * @param map the signal appearance map.
042     * @param sigSystem the signal system.
043     */
044    abstract public void setAspectNames(@Nonnull SignalAppearanceMap map, 
045                               @Nonnull SignalSystem sigSystem);
046
047    /**
048     * Can this pane edit a specific mast object, i.e. an object of its type?
049     *
050     * @param mast the SignalMast to possibly display
051     * @return true if this pane can handle that mast type; false if can't
052     */
053    abstract public boolean canHandleMast(@Nonnull SignalMast mast);
054
055    /**
056     * Load this pane with information from a mast.
057     * Do not invoke this if {@link #canHandleMast(SignalMast)} on that mast returns false.
058     *
059     * @param mast the SignalMast to display or null to reset a previous setting
060     */
061    abstract public void setMast(SignalMast mast);
062    
063    /**
064     * Called to either "create and register" a new, or "update" an existing mast from the given information.
065     *
066     * @param sigsysname the name of the signal system in use
067     * @param mastname   the mast type name
068     * @param username   user name value
069     * @return false if the operation failed, in which case the user should have already been notified
070     */
071    abstract public boolean createMast(@Nonnull String sigsysname, @Nonnull String mastname, @Nonnull String username);
072    
073    /**
074     * @return human-preferred name for type of signal mast, in local language
075     */
076    @Nonnull abstract public String getPaneName();
077    
078    final protected static int NOTIONAL_ASPECT_COUNT = 12;  // size of maps, not critical
079
080    static public abstract class SignalMastAddPaneProvider implements JmriServiceProviderInterface {
081        /**
082         * Is this pane available, given the current configuration of the program?
083         * In other words, are all necessary managers and other objects present?
084         * @return always true.
085         */
086        public boolean isAvailable() { return true; }
087
088        /**
089         * @return Human-prefered name for type of signal mast, in local language
090         */
091        @Nonnull abstract public String getPaneName();
092        
093        /**
094         * @return A new instance of this SignalMastAddPane class
095         */
096        @Nonnull abstract public SignalMastAddPane getNewPane();
097        
098        /**
099         * Get all available instances as an {@link Collections#unmodifiableMap}
100         * between the (localized) name and the pane. Note that this is a SortedMap in 
101         * name order.
102         * @return all instance map sorted in name order.
103         */
104        final static public Map<String, SignalMastAddPaneProvider> getInstancesMap() {
105            if (instanceMap == null) loadInstances();
106            return Collections.unmodifiableMap(instanceMap);
107        }
108    
109        /**
110         * Get all available instances as an {@link Collections#unmodifiableCollection}
111         * between the (localized) name and the pane. 
112         * @return unmodifiable collection.
113         */
114        final static public Collection<SignalMastAddPaneProvider> getInstancesCollection() {
115            if (instanceMap == null) loadInstances();
116            return Collections.unmodifiableCollection(instanceMap.values());
117        }
118    
119        /**
120         * Load all the available instances. Note this only runs
121         * once; there's no reloading once the program is running.
122         */
123        final static public void loadInstances() {
124            if (instanceMap != null) return;
125        
126            instanceMap = new TreeMap<>();  // sorted map, in string order on key
127        
128            java.util.ServiceLoader.load(SignalMastAddPaneProvider.class).forEach((pane) -> {
129                 if (pane.isAvailable()) {
130                    instanceMap.put(pane.getPaneName(), pane);
131                }
132            });
133
134        }
135
136        static volatile Map<String, SignalMastAddPaneProvider> instanceMap = null;
137    }
138    
139}