001package jmri.jmrix.can;
002
003import java.util.Comparator;
004import java.util.HashMap;
005import java.util.Map;
006import java.util.ResourceBundle;
007import java.util.Set;
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.jmrix.ConfiguringSystemConnectionMemo;
012import jmri.jmrix.DefaultSystemConnectionMemo;
013import jmri.jmrix.can.ConfigurationManager.SubProtocol;
014import jmri.jmrix.can.ConfigurationManager.ProgModeSwitch;
015import jmri.util.NamedBeanComparator;
016
017import jmri.util.startup.StartupActionFactory;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Lightweight class to denote that a system is active, and provide general
023 * information.
024 * <p>
025 * As various CAN adapters, can work with different CAN Bus systems, the adapter
026 * memo is generic for all adapters, it then uses a ConfigurationManager for
027 * each of the CAN Bus systems. Any requests for provision or configuration is
028 * passed on to the relevant ConfigurationManager to handle.
029 *
030 * @author Kevin Dickerson Copyright (C) 2012
031 * @author Andrew Crosland Copyright (C) 2021
032 */
033public class CanSystemConnectionMemo extends DefaultSystemConnectionMemo implements ConfiguringSystemConnectionMemo {
034    // This user name will be overwritten by the adapter and saved to the connection config.
035    public static final String DEFAULT_USERNAME = "CAN";
036
037    private boolean protocolOptionsChanged = false;
038
039    /**
040     * Create a new CanSystemConnectionMemo.
041     * Default prefix: M
042     * Default Username: CAN
043     */
044    public CanSystemConnectionMemo() {
045        super("M", DEFAULT_USERNAME);
046    }
047
048    /**
049     * Create a new CanSystemConnectionMemo.
050     * Allows for default systemPrefix other than "M"
051     * Default Username: CAN
052     * @param prefix System prefix to use, e.g. M.
053     */
054    public CanSystemConnectionMemo(String prefix) {
055        super(prefix, DEFAULT_USERNAME);
056    }
057
058    protected final void storeCanMemotoInstance() {
059        register(); // registers general type
060        InstanceManager.store(this, CanSystemConnectionMemo.class); // also register as specific type
061    }
062
063    protected String _protocol = ConfigurationManager.MERGCBUS;
064    protected SubProtocol _subProtocol = SubProtocol.CBUS;
065    protected ProgModeSwitch _progModeSwitch = ProgModeSwitch.NONE;
066    protected boolean _supportsCVHints = false; // Support for CV read hint values
067    private boolean _multipleThrottles = true;  // Support for multiple throttles
068    private boolean _powerOnArst = true;        // Turn power on if ARST opcode received
069
070    jmri.jmrix.swing.ComponentFactory cf = null;
071
072    protected TrafficController tm;
073
074    /**
075     * Set Connection Traffic Controller.
076     * @param tm System Connection Traffic Controller
077     */
078    public void setTrafficController(TrafficController tm) {
079        this.tm = tm;
080    }
081
082    /**
083     * Get Connection Traffic Controller.
084     * @return System Connection Traffic Controller
085     */
086    public TrafficController getTrafficController() {
087        return tm;
088    }
089
090    private jmri.jmrix.can.ConfigurationManager manager;
091
092    private final Map<String, Map<String, String>> protocolOptions = new HashMap<>();
093
094    /**
095     * {@inheritDoc }
096     * Searches ConfigurationManager for class before super.
097     */
098    @Override
099    public boolean provides(Class<?> type) {
100        if (getDisabled()) {
101            return false;
102        }
103        if (manager == null) {
104            return false;
105        }
106        if (type.equals(jmri.GlobalProgrammerManager.class)) {
107            jmri.GlobalProgrammerManager mgr = get(jmri.GlobalProgrammerManager.class);
108            if (mgr == null) return false;
109            return mgr.isGlobalProgrammerAvailable();
110        }
111        if (type.equals(jmri.AddressedProgrammerManager.class)) {
112            jmri.AddressedProgrammerManager mgr = get(jmri.AddressedProgrammerManager.class);
113            if (mgr == null) return false;
114            return mgr.isAddressedModePossible();
115        }
116
117        boolean result = manager.provides(type);
118        if(result) {
119           return true;
120        } else {
121           return super.provides(type);
122        }
123    }
124
125    /**
126     * {@inheritDoc }
127     * Searches ConfigurationManager for class before super.
128     */
129    @SuppressWarnings("unchecked")
130    @Override
131    public <T> T get(Class<T> T) {
132        if (manager != null && !getDisabled()) {
133            T existing = manager.get(T);
134            if ( existing !=null ) {
135                return existing;
136            }
137        }
138        return super.get(T);
139    }
140
141    /**
142     * Get a class directly from the memo Class Object Map.
143     * @param <T> Class type
144     * @param T Class type
145     * @return object in class object map, or null if not present.
146     */
147    @SuppressWarnings("unchecked")
148    public <T> T getFromMap(Class<?> T) {
149        return (T) classObjectMap.get(T);
150    }
151
152    /**
153     * Get the Protocol in use by the CAN Connection.
154     * e.g. ConfigurationManager.SPROGCBUS
155     * @return ConfigurationManager constant of protocol.
156     */
157    public String getProtocol() {
158        return _protocol;
159    }
160
161    /**
162     * Set the protocol in use by the connection.
163     * @param protocol e.g. ConfigurationManager.SPROGCBUS
164     */
165    public void setProtocol(String protocol) {
166        StartupActionFactory old = getActionFactory();
167        if (null != protocol) {
168            _protocol = protocol;
169            switch (protocol) {
170                case ConfigurationManager.SPROGCBUS:
171                case ConfigurationManager.MERGCBUS:
172                    manager = new jmri.jmrix.can.cbus.CbusConfigurationManager(this);
173                    break;
174                case ConfigurationManager.OPENLCB:
175                    manager = new jmri.jmrix.openlcb.OlcbConfigurationManager(this);
176                    break;
177                case ConfigurationManager.RAWCAN:
178                    manager = new jmri.jmrix.can.CanConfigurationManager(this);
179                    break;
180                case ConfigurationManager.TEST:
181                    manager = new jmri.jmrix.can.nmranet.NmraConfigurationManager(this);
182                    break;
183                default:
184                    break;
185            }
186        }
187        firePropertyChange("actionFactory", old, getActionFactory());
188    }
189
190    /**
191     * Get ENUM of the sub protocol.
192     * @return the sub protocol in use, e.g. SubProtocol.CBUS
193     */
194    public SubProtocol getSubProtocol() {
195        return _subProtocol;
196    }
197
198    /**
199     * Set the sub protocol ENUM.
200     * @param sp e.g. SubProtocol.CBUS
201     */
202    public void setSubProtocol(SubProtocol sp) {
203        if (null != sp) {
204            _subProtocol = sp;
205        }
206    }
207
208    /**
209     * Get the state of the programming mode switch which indicates what combination
210     * of service and/or ops mode programming is supported by the connection.
211     *
212     * @return the supported modes
213     */
214    public ProgModeSwitch getProgModeSwitch() {
215        return _progModeSwitch;
216    }
217
218    public void setProgModeSwitch(ProgModeSwitch pms) {
219        if (null != pms) {
220            _progModeSwitch = pms;
221        }
222    }
223
224    /**
225     * Some connections support only a single throttle, e.g., a service mode programmer
226     * that allows for test running of a single loco.
227     *
228     * @return true if mutltiple throttles are available
229     */
230    public boolean hasMultipleThrottles() {
231        return _multipleThrottles;
232    }
233
234    public void setMultipleThrottles(boolean b) {
235        _multipleThrottles = b;
236    }
237
238    /**
239     * Get the CV hint support flag
240     *
241     * @return true if CV hints are supported
242     */
243    public boolean supportsCVHints() {
244        return _supportsCVHints;
245    }
246
247    public void setSupportsCVHints(boolean b) {
248        _supportsCVHints = b;
249    }
250
251    /**
252     * Get the behaviour on ARST opcode
253     *
254     * @return true if track power is on after ARST
255     */
256    public boolean powerOnArst() {
257        return _powerOnArst;
258    }
259
260    public void setPowerOnArst(boolean b) {
261        _powerOnArst = b;
262    }
263
264    /**
265     * Configure the common managers for Can connections.
266     * {@inheritDoc }
267     * Calls ConfigurationManager.configureManagers
268     * Stores Can Memo to Instance to indicate available.
269     *
270     */
271    @Override
272    public void configureManagers() {
273        if (manager != null) {
274            manager.configureManagers();
275        }
276        storeCanMemotoInstance();
277    }
278
279    /**
280     * {@inheritDoc }
281     */
282    @Override
283    protected ResourceBundle getActionModelResourceBundle() {
284        if (manager == null) {
285            return null;
286        }
287        return manager.getActionModelResourceBundle();
288    }
289
290    /**
291     * {@inheritDoc }
292     */
293    @Override
294    public <B extends NamedBean> Comparator<B> getNamedBeanComparator(Class<B> type) {
295        return new NamedBeanComparator<>();
296    }
297
298    /**
299     * Enumerate all protocols that have options set.
300     *
301     * @return set of protocol names.
302     */
303    public Set<String> getProtocolsWithOptions() {
304        return protocolOptions.keySet();
305    }
306
307    /**
308     * Get all options we have set (saved in the connection XML) for a given protocol type.
309     *
310     * @param protocol String name of the protocol.
311     * @return map of known protocol options to values, or empty map.
312     */
313    @Nonnull
314    public Map<String, String> getProtocolAllOptions(String protocol) {
315        return protocolOptions.getOrDefault(protocol, new HashMap<>());
316    }
317
318    /**
319     * Get a single option of a single protocol, or null if not present.
320     *
321     * @param protocol name of the protocol.
322     * @param option name of the option.
323     * @return null if option has never been set; or the option value if set.
324     */
325    public synchronized String getProtocolOption(String protocol, String option) {
326        if (!protocolOptions.containsKey(protocol)) return null;
327        Map<String, String> m = getProtocolAllOptions(protocol);
328        return m.getOrDefault(option, null);
329    }
330
331    /**
332     * Sets a protocol option. This list will be persisted when the connection gets saved.
333     *
334     * @param protocol name of the protocol
335     * @param option name of the option
336     * @param value option value
337     */
338    public synchronized void setProtocolOption(String protocol, String option, String value) {
339        log.debug("Setting protocol option {} {} := {}", protocol, option, value);
340        if (value == null) return;
341        Map<String, String> m = protocolOptions.computeIfAbsent(protocol, k -> new HashMap<>());
342        String oldValue = m.get(option);
343        if (value.equals(oldValue)) return;
344        m.put(option, value);
345        protocolOptionsChanged = true;
346    }
347
348    @Override
349    public boolean isDirty() {
350        return super.isDirty() || protocolOptionsChanged;
351    }
352
353    @Override
354    public boolean isRestartRequired() {
355        return super.isRestartRequired() || protocolOptionsChanged;
356    }
357
358    /**
359     * Custom interval of 100ms.
360     * {@inheritDoc}
361     */
362    @Override
363    public int getDefaultOutputInterval(){
364        return 100;
365    }
366
367    /**
368     * {@inheritDoc }
369     */
370    @Override
371    public void dispose() {
372        super.dispose(); // remove class map object items before manager config
373        if (manager != null) {
374            manager.dispose();
375        }
376        tm = null;
377    }
378
379    private static final Logger log = LoggerFactory.getLogger(CanSystemConnectionMemo.class);
380
381}