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
072            String sigsysname, @Nonnull
073                    String mastname, @Nonnull
074                            String username);
075    
076    /**
077     * @return human-preferred name for type of signal mast, in local language
078     */
079    @Nonnull abstract public String getPaneName();
080    
081    final protected static int NOTIONAL_ASPECT_COUNT = 12;  // size of maps, not critical
082
083    static public abstract class SignalMastAddPaneProvider implements JmriServiceProviderInterface {
084        /**
085         * Is this pane available, given the current configuration of the program?
086         * In other words, are all necessary managers and other objects present?
087         * @return always true.
088         */
089        public boolean isAvailable() { return true; }
090
091        /**
092         * @return Human-prefered name for type of signal mast, in local language
093         */
094        @Nonnull abstract public String getPaneName();
095        
096        /**
097         * @return A new instance of this SignalMastAddPane class
098         */
099        @Nonnull abstract public SignalMastAddPane getNewPane();
100        
101        /**
102         * Get all available instances as an {@link Collections#unmodifiableMap}
103         * between the (localized) name and the pane. Note that this is a SortedMap in 
104         * name order.
105         * @return all instance map sorted in name order.
106         */
107        final static public Map<String, SignalMastAddPaneProvider> getInstancesMap() {
108            if (instanceMap == null) loadInstances();
109            return Collections.unmodifiableMap(instanceMap);
110        }
111    
112        /**
113         * Get all available instances as an {@link Collections#unmodifiableCollection}
114         * between the (localized) name and the pane. 
115         * @return unmodifiable collection.
116         */
117        final static public Collection<SignalMastAddPaneProvider> getInstancesCollection() {
118            if (instanceMap == null) loadInstances();
119            return Collections.unmodifiableCollection(instanceMap.values());
120        }
121    
122        /**
123         * Load all the available instances. Note this only runs
124         * once; there's no reloading once the program is running.
125         */
126        final static public void loadInstances() {
127            if (instanceMap != null) return;
128        
129            instanceMap = new TreeMap<>();  // sorted map, in string order on key
130        
131            java.util.ServiceLoader.load(SignalMastAddPaneProvider.class).forEach((pane) -> {
132                 if (pane.isAvailable()) {
133                    instanceMap.put(pane.getPaneName(), pane);
134                }
135            });
136
137        }
138
139        static volatile Map<String, SignalMastAddPaneProvider> instanceMap = null;
140    }
141    
142}