001package jmri.jmrix.can.cbus.simulator;
002
003import java.util.*;
004
005import javax.annotation.CheckForNull;
006import javax.annotation.Nonnull;
007
008import jmri.jmrix.can.CanSystemConnectionMemo;
009import jmri.jmrix.can.cbus.node.CbusNode;
010import jmri.jmrix.can.cbus.node.CbusNodeConstants;
011
012import jmri.spi.JmriServiceProviderInterface;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * Pane for configuring events in a CBUS module
019 * 
020 * Definition of objects to handle configuring a CBUS module.
021 *
022 * Implementing classes <em>must</em> be registered as service providers of this
023 * type to be recognized and usable.
024 * <p>
025 * General design documentation is available on the 
026 * <a href="http://jmri.org/help/en/html/doc/Technical/SystemStructure.shtml">Structure of External System Connections page</a>.
027 *
028 * @author Andrew Crosland Copyright (C) 2021
029 * @author Steve Young Copyright (C) 2022
030 * @see java.util.ServiceLoader
031 */
032public abstract class CbusSimulatedModuleProvider implements JmriServiceProviderInterface {
033
034    /**
035     * Get the Manufacturer ID.
036     * @return manufacturer ID code.
037     */
038    public abstract int getManufacturerId();
039    
040    /**
041     * Get the Manufacturer Module ID.
042     * @return manufacturer Module ID code.
043     */
044    public abstract int getModuleId();
045    
046    /**
047     * For a given CbusDummyNode, configure it to the Simulation.
048     * This may include Node Parameters, Node Variables, events and event variables.
049     * @param node the Node to set to.
050     */
051    public abstract void configureDummyNode(@Nonnull CbusNode node);
052    
053    /**
054     * Descriptive String of Module Type.
055     * For use in selection menus etc.
056     * @return descriptive string of simulated module.
057     */
058    @Nonnull
059    public String getModuleType() {
060        StringBuilder s = new StringBuilder();
061        s.append(CbusNodeConstants.getManu(getManufacturerId()));
062        s.append(" ");
063        s.append(CbusNodeConstants.getModuleType(getManufacturerId(), getModuleId()));
064        return s.toString();
065    }
066
067    /**
068     * Descriptive Tooltip for Module Simulation.
069     * For use in selection menus etc.
070     * @return tooltip for the module.
071     */
072    @Nonnull
073    public String getToolTipText() {
074        return "Simulation of " + CbusNodeConstants.getModuleTypeExtra(getManufacturerId(),getModuleId());
075    }
076
077    /**
078     * Create a new CbusDummyNode of the implementing class type.
079     * @param nodeNumber Initial Node Number.
080     * @param memo System Connection to use.
081     * @return new Dummy Node of implementing class type.
082     */
083    @Nonnull
084    public CbusDummyNode getNewDummyNode(CanSystemConnectionMemo memo, int nodeNumber ){
085        CbusDummyNode nd = createNewDummyNode(memo, nodeNumber);
086        configureDummyNode(nd);
087        CbusNodeConstants.setTraits( nd );
088        log.info("Simulated CBUS Module: {}", getModuleType() );
089        return nd;
090    }
091    
092    // future classes may want to override this, eg CANMIO-U or CANCMD
093    protected CbusDummyNode createNewDummyNode(CanSystemConnectionMemo memo, int nodeNumber ){
094        return new CbusDummyNode(memo, nodeNumber);
095    }
096
097    /**
098     * Checks if a Node Manufacturer and Module ID matches this module.
099     * @param nd the Node to test against, can be null.
100     * @return true if they match, else false.
101     */
102    public boolean matchesManuAndModuleId(@CheckForNull CbusDummyNode nd) {
103        if ( nd == null ) {
104            return false;
105        }
106        return nd.getNodeParamManager().getParameter(1) == getManufacturerId()
107            && nd.getNodeParamManager().getParameter(3) == getModuleId();
108    }
109
110    /**
111     * Get a module provider from a module name.
112     * 
113     * @param name of the module
114     * @return the module provider, null if not known
115     */
116    @CheckForNull
117    final static public CbusSimulatedModuleProvider getProviderByName(String name) {
118        loadInstances();
119        return instanceMap.get(name);
120    }
121
122    /**
123     * Get all available instances as an {@link Collections#unmodifiableCollection}.
124     * 
125     * @return unmodifiable collection.
126     */
127    @Nonnull
128    final static public Collection<CbusSimulatedModuleProvider> getInstancesCollection() {
129        loadInstances();
130        return Collections.unmodifiableCollection(instanceMap.values());
131    }
132
133    /**
134     * Load all the available instances. Note this only runs
135     * once; there's no reloading once the program is running.
136     */
137    static private void loadInstances() {
138        if (instanceMap != null) return;
139
140        instanceMap = new TreeMap<>();  // sorted map, in string order on key
141
142        java.util.ServiceLoader.load(CbusSimulatedModuleProvider.class).forEach((module) -> {
143            if (!instanceMap.containsKey(module.getModuleType())) {
144                instanceMap.put(module.getModuleType(), module);
145            }
146        });
147
148    }
149
150    private static volatile Map<String, CbusSimulatedModuleProvider> instanceMap = null;
151
152    private final static Logger log = LoggerFactory.getLogger(CbusSimulatedModuleProvider.class);
153}