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