001package jmri.jmrix.anyma;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.util.Comparator;
006import java.util.ResourceBundle;
007
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.Manager.NameValidity;
012import jmri.jmrix.ConfiguringSystemConnectionMemo;
013import jmri.jmrix.DefaultSystemConnectionMemo;
014import jmri.util.NamedBeanComparator;
015
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Minimal SystemConnectionMemo for anyma dmx systems.
021 *
022 * @author George Warner Copyright (c) 2017-2018
023 * @since 4.9.6
024 */
025public class AnymaDMX_SystemConnectionMemo extends DefaultSystemConnectionMemo implements ConfiguringSystemConnectionMemo {
026
027    private boolean configured = false;
028
029    /**
030     * constructor
031     */
032    public AnymaDMX_SystemConnectionMemo() {
033        this("D", AnymaDMX_ConnectionTypeList.ANYMA_DMX); // default to "D" prefix
034        log.debug("* Constructor()");
035    }
036
037    /**
038     * constructor.
039     * @param prefix system prefix.
040     * @param userName system username.
041     */
042    public AnymaDMX_SystemConnectionMemo(@Nonnull String prefix, @Nonnull String userName) {
043        super(prefix, userName);
044
045        log.debug("* Constructor ({}, {})", prefix, userName);
046
047        register(); // registers general type
048        InstanceManager.store(this, AnymaDMX_SystemConnectionMemo.class); // also register as specific type
049    }
050
051    private AnymaDMX_TrafficController trafficController = null;
052
053    /**
054     * get the traffic controller
055     *
056     * @return the traffic controller
057     */
058    protected AnymaDMX_TrafficController getTrafficController() {
059        return trafficController;
060    }
061
062    /**
063     * set the traffic controller
064     *
065     * @param trafficController the traffic controller
066     */
067    protected void setTrafficController(AnymaDMX_TrafficController trafficController) {
068        this.trafficController = trafficController;
069    }
070
071    /**
072     * public method to get the user name for a valid system name
073     *
074     * @param systemName the system name
075     * @return "" (null string) if system name is not valid or does not exist
076     */
077    public String getUserNameFromSystemName(String systemName) {
078        log.debug("* getUserNameFromSystemName('{}')", systemName);
079        String result = "";        // not any known light
080        int offset = checkSystemPrefix(systemName);
081        if (offset > 0) {
082            if (systemName.length() > offset) {
083                if (systemName.charAt(offset) == 'L') {
084                    Light lgt = null;
085                    lgt = InstanceManager.lightManagerInstance().getBySystemName(systemName);
086                    if (lgt != null) {
087                        result = lgt.getUserName();
088                    }
089                }
090            }
091        }
092        return result;
093    }
094
095    /**
096     * Public static method to parse a anyma dmx system name and return the
097     * channel number. Notes:
098     * <ul>
099     * <li>Channels are numbered from 1 to 512.</li>
100     * <li>Does not check whether that node is defined on current system.</li>
101     * </ul>
102     * @param systemName system name.
103     * @return 0 if an error is found.
104     */
105    public int getChannelFromSystemName(String systemName) {
106        int result = 0;
107        log.debug("* getChannelFromSystemName('{}')", systemName);
108
109        int offset = checkSystemPrefix(systemName);
110        if (offset > 0) {
111            if (validSystemNameFormat(systemName, systemName.charAt(offset)) == NameValidity.VALID) {
112                // Find the beginning of the channel number field
113                int k = 0;
114                for (int i = offset; i < systemName.length(); i++) {
115                    if (systemName.charAt(i) == 'L') {
116                        k = i + 1;
117                        break;
118                    }
119                }
120                if (k > offset) {    // k = position of "L" char in name
121                    try {
122                        result = Integer.parseInt(systemName.substring(k));
123                    } catch (NumberFormatException e) {
124                        log.warn("invalid character in channel number field of anyma dmx system name: {}", systemName);
125                    }
126                }
127            } else {
128                log.error("No point in normalizing if a valid system name format is not present");
129            }
130        } else {
131            log.error("invalid system prefix in anyma dmx system name in getChannelFromSystemName: {}", systemName);
132        }
133        return result;
134    }
135
136    /**
137     * Public static method to check and skip the System Prefix string on a
138     * system name.
139     *
140     * @param systemName system name string.
141     * @return offset of the 1st character past the prefix, or -1 if not valid
142     *         for this connection
143     */
144    public int checkSystemPrefix(String systemName) {
145        log.debug("* checkSystemPrefix('{}')", systemName);
146        int result = -1;
147        if (systemName.startsWith(getSystemPrefix())) {
148            result = getSystemPrefix().length();
149        }
150        return result;
151    }
152
153    /**
154     * Public static method to convert one format anyma dmx system name to the
155     * alternate format.
156     *
157     * @param systemName system name string.
158     * @return "" (empty string) if the supplied system name does not have a
159     *         valid format, or if there is no representation in the alternate
160     *         naming scheme
161     */
162    public String convertSystemNameToAlternate(String systemName) {
163        log.debug("* convertSystemNameToAlternate('{}')", systemName);
164        String result = "";
165
166        int offset = checkSystemPrefix(systemName);
167        if (offset > 0) {
168            if (validSystemNameFormat(systemName, systemName.charAt(offset)) == NameValidity.VALID) {
169                int channelNum = Integer.parseInt(systemName.substring(offset + 1));
170                result = systemName.substring(0, offset + 1) + Integer.toString(channelNum);
171            } else {
172                log.error("valid system name format not present in anyma dmx system name: {}", systemName);
173            }
174        } else {
175            log.error("invalid system prefix in anyma dmx system name in convertSystemNameToAlternate: {}", systemName);
176        }
177        return result;
178    }
179
180    /**
181     * Public static method to validate system name format.
182     * Does not check whether that node is defined on current system.
183     *
184     * @param systemName proposed system name.
185     * @param type bean type, only L supported.
186     * @return enum indicating current validity, which might be just as a prefix
187     */
188    public NameValidity validSystemNameFormat(@Nonnull String systemName, char type) {
189        log.debug("* validSystemNameFormat('{}', '{}')", systemName, type);
190        NameValidity result = NameValidity.INVALID; // assume failure (pessimist!)
191
192        int offset = checkSystemPrefix(systemName);
193        if (offset > 0) {
194            if (systemName.charAt(offset) == type) {
195                // This is a CLnnnxxx pattern address
196                int num;
197                try {
198                    num = Integer.parseInt(systemName.substring(offset + 1));
199                    if ((num >= 1) && (num <= 512)) {
200                        result = NameValidity.VALID;
201                    } else {
202                        log.debug("number field out of range in anyma dmx system name: {}", systemName);
203                    }
204                } catch (NumberFormatException e) {
205                    log.debug("invalid character in number field of anyma dmx system name: {}", systemName);
206                }
207            } else {
208                log.error("invalid type character in anyma dmx system name: {}", systemName);
209            }
210        } else {
211            log.error("invalid system prefix in anyma dmx system name in validSystemNameFormat: {}", systemName);
212        }
213        return result;
214    }
215
216    /**
217     * Public static method to validate anyma dmx system name for configuration.
218     * Does validate node number and system prefix.
219     *
220     * @param systemName anya dmx systemName.
221     * @param type bean type, only L supported.
222     * @return 'true' if system name has a valid meaning in current
223     *         configuration, else returns 'false'.
224     */
225    public boolean validSystemNameConfig(String systemName, char type) {
226        log.debug("* validSystemNameConfig('{}', '{}')", systemName, type);
227        boolean result = false; // assume failure (pessimist!)
228        if (validSystemNameFormat(systemName, type) == NameValidity.VALID) {
229            if (type == 'L') {
230                int channel = getChannelFromSystemName(systemName);
231                if ((channel >= 1) && (channel <= 512)) {
232                    result = true;  // The channel is valid
233                }
234            } else {
235                log.error("Invalid type specification in validSystemNameConfig call");
236            }
237        } else {
238            log.error("valid system name format is not present");
239        }
240        return result;
241    }
242
243    /**
244     * Public static method to parse a anyma dmx system name and return the Usb
245     * Node Address
246     * <p>
247     * Nodes are numbered from 0 - 127. Does not check whether that node is
248     * defined on current system.
249     *
250     * @param systemName system name.
251     * @return '-1' if invalid systemName format or if the node is not found.
252     */
253    public int getNodeAddressFromSystemName(String systemName) {
254        int result = -1;    // assume failure (pessimist!)
255        log.debug("* getNodeAddressFromSystemName('{}')", systemName);
256        int offset = checkSystemPrefix(systemName);
257        if (offset > 0) {
258            if (systemName.charAt(offset) == 'L') {
259                int num = Integer.parseInt(systemName.substring(offset + 1));
260                if (num > 0) {
261                    result = num;
262                } else {
263                    log.warn("invalid anyma dmx system name: {}", systemName);
264                }
265            } else {
266                log.error("invalid character in header field of system name: {}", systemName);
267            }
268        }
269        return result;
270    }
271
272    /**
273     * {@inheritDoc}
274     */
275    @Override
276    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
277            justification = "This system connection doesn't support ConsistManager from the super class")
278    public boolean provides(Class<?> c) {
279        return (get(c) != null);
280    }
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286    @SuppressWarnings("unchecked")
287    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
288            justification = "This system connection doesn't support ConsistManager from the super class")
289    public <T> T get(Class<T> T) {
290        T result = null; // nothing by default
291        log.debug("* get({})", T.toString());
292        if (!getDisabled()) {
293            if (!configured) {
294                configureManagers();
295            }
296            if (T.equals(LightManager.class)) {
297                result = (T) getLightManager();
298            }
299        }
300        return result;
301    }
302
303    /**
304     * Configure the common managers for anyma dmx connections. This puts the
305     * common manager config in one place.
306     */
307    @Override
308    public void configureManagers() {
309        log.debug("* configureManagers()");
310        InstanceManager.setLightManager(getLightManager());
311
312        if (configured) {
313            log.warn("calling configureManagers for a second time", new Exception("traceback"));
314        }
315        configured = true;
316    }
317
318    /**
319     * get the LightManager
320     *
321     * @return the LightManager
322     */
323    public UsbLightManager getLightManager() {
324        log.debug("* getLightManager()");
325        if (getDisabled()) {
326            return null;
327        }
328        return (UsbLightManager) classObjectMap.computeIfAbsent(LightManager.class, (Class<?> c) -> new UsbLightManager(this));
329    }
330
331    /**
332     * get the action model resource bundle
333     *
334     * @return the ResourceBundle
335     */
336    @Override
337    protected ResourceBundle getActionModelResourceBundle() {
338        log.debug("* getActionModelResourceBundle()");
339        return null;
340    }
341
342    @Override
343    public <B extends NamedBean> Comparator<B> getNamedBeanComparator(Class<B> type) {
344        return new NamedBeanComparator<>();
345    }
346
347    /**
348     * dispose
349     */
350    @Override
351    public void dispose() {
352        log.debug("* dispose()");
353        InstanceManager.deregister(this, AnymaDMX_SystemConnectionMemo.class);
354        super.dispose();
355    }
356
357    private final static Logger log
358            = LoggerFactory.getLogger(AnymaDMX_SystemConnectionMemo.class);
359}