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