001package jmri.jmrix.cmri.serial;
002
003import java.util.Locale;
004
005import javax.annotation.Nonnull;
006
007import jmri.*;
008import jmri.jmrix.cmri.CMRISystemConnectionMemo;
009import jmri.managers.AbstractTurnoutManager;
010import jmri.util.swing.JmriJOptionPane;
011
012/**
013 * Implement turnout manager for CMRI serial systems.
014 * <p>
015 * System names are "CTnnn", where C is the user configurable system prefix,
016 * nnn is the turnout number without padding.
017 *
018 * @author Bob Jacobsen Copyright (C) 2003
019 */
020public class SerialTurnoutManager extends AbstractTurnoutManager {
021
022    public SerialTurnoutManager(CMRISystemConnectionMemo memo) {
023       super(memo);
024    }
025
026    /**
027     * {@inheritDoc}
028     */
029    @Override
030    @Nonnull
031    public CMRISystemConnectionMemo getMemo() {
032        return (CMRISystemConnectionMemo) memo;
033    }
034
035    /**
036     * {@inheritDoc}
037     */
038    @Nonnull
039    @Override
040    protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException {
041        // validate the system name, and normalize it
042        String sName = getMemo().normalizeSystemName(systemName);
043        if (sName.isEmpty()) {
044            // system name is not valid
045            throw new IllegalArgumentException("Cannot create System Name from " + systemName);
046        }
047        // does this turnout already exist
048        Turnout t = getBySystemName(sName);
049        if (t != null) {
050            return t;
051        }
052        // check under alternate name
053        String altName = getMemo().convertSystemNameToAlternate(sName);
054        t = getBySystemName(altName);
055        if (t != null) {
056            return t;
057        }
058
059        // check if the addressed output bit is available
060        int nAddress = getMemo().getNodeAddressFromSystemName(sName);
061        if (nAddress == -1) {
062            throw new IllegalArgumentException("Cannot get Node Address from System Name " + systemName + " " + sName);
063        }
064        int bitNum = getMemo().getBitFromSystemName(sName);
065        if (bitNum == 0) {
066            throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName + " " + sName);
067        }
068        String conflict = getMemo().isOutputBitFree(nAddress, bitNum);
069        if ((!conflict.isEmpty()) && (!conflict.equals(sName))) {
070            log.error("{} assignment conflict with {}.", sName, conflict);
071            throw new IllegalArgumentException(Bundle.getMessage("ErrorAssignDialog", bitNum, conflict));
072        }
073
074        // create the turnout
075        t = new SerialTurnout(sName, userName, getMemo());
076
077        // does system name correspond to configured hardware
078        if (!getMemo().validSystemNameConfig(sName, 'T', getMemo().getTrafficController())) {
079            // system name does not correspond to configured hardware
080            log.warn("Turnout '{}' refers to an undefined Serial Node.", sName);
081        }
082        return t;
083    }
084
085    /**
086     * Get from the user, the number of addressed bits used to control a
087     * turnout.
088     * <p>
089     * Normally this is 1, and the default routine returns 1
090     * automatically. Turnout Managers for systems that can handle multiple
091     * control bits should override this method with one which asks the user to
092     * specify the number of control bits. If the user specifies more than one
093     * control bit, this method should check if the additional bits are
094     * available (not assigned to another object). If the bits are not
095     * available, this method should return 0 for number of control bits, after
096     * informing the user of the problem. This function is called whenever a new
097     * turnout is defined in the Turnout table. It can also be used to set up
098     * other turnout control options, such as pulsed control of turnout
099     * machines.
100     */
101    @Override
102    public int askNumControlBits(@Nonnull String systemName) {
103
104        // ask user how many bits should control the turnout - 1 or 2
105        int iNum = selectNumberOfControlBits();
106        if (iNum == JmriJOptionPane.CLOSED_OPTION) {
107            /* user cancelled without selecting an option */
108            iNum = 1;
109            log.warn("User cancelled without selecting number of output bits. Defaulting to 1.");
110        } else {
111            iNum = iNum + 1;
112        }
113
114        if (iNum == 2) {
115            // check if the second output bit is available
116            int nAddress = getMemo().getNodeAddressFromSystemName(systemName);
117            if (nAddress == -1) {
118                return 0;
119            }
120            int bitNum = getMemo().getBitFromSystemName(systemName);
121            if (bitNum == 0) {
122                return 0;
123            }
124            bitNum = bitNum + 1;
125            String conflict = getMemo().isOutputBitFree(nAddress, bitNum);
126            if (!conflict.equals("")) {
127                log.error("Assignment conflict with {}. Turnout not created.", conflict);
128                notifySecondBitConflict(conflict, bitNum);
129                return 0;
130            }
131        }
132
133        return (iNum);
134    }
135
136    /**
137     * Get from the user, the type of output to be used bits to control a
138     * turnout.
139     * <p>
140     * Normally this is 0 for 'steady state' control, and the default
141     * routine returns 0 automatically. Turnout Managers for systems that can
142     * handle pulsed control as well as steady state control should override
143     * this method with one which asks the user to specify the type of control
144     * to be used. The routine should return 0 for 'steady state' control, or n
145     * for 'pulsed' control, where n specifies the duration of the pulse
146     * (normally in seconds).
147     */
148    @Override
149    public int askControlType(@Nonnull String systemName) {
150        // ask if user wants 'steady state' output (stall motors, e.g., Tortoises) or
151        // 'pulsed' output (some turnout controllers).
152        int iType = selectOutputType();
153        if (iType == JmriJOptionPane.CLOSED_OPTION) {
154            /* user cancelled without selecting an output type */
155            iType = 0;
156            log.warn("User cancelled without selecting output type. Defaulting to 'steady state'.");
157        }
158        // Note: If the user selects 'pulsed', this routine defaults to 1 second.
159        return (iType);
160    }
161
162    /**
163     * Public method to allow user to specify one or two output bits for turnout
164     * control.
165     *
166     * @return  JmriJOptionPane.CLOSED_OPTION if the user cancelled without selecting.
167     *          0 if the user selected BitOption1,
168     *          1 if the user selected BitOption2.
169     */
170    public int selectNumberOfControlBits() {
171        return JmriJOptionPane.showOptionDialog(null,
172                Bundle.getMessage("QuestionBitsDialog"),
173                Bundle.getMessage("CmriTurnoutTitle"), JmriJOptionPane.DEFAULT_OPTION,
174                JmriJOptionPane.QUESTION_MESSAGE,
175                null, new String[]{Bundle.getMessage("BitOption1"), Bundle.getMessage("BitOption2")}, Bundle.getMessage("BitOption1"));
176    }
177
178    /**
179     * Public method to allow user to specify pulsed or steady state for two
180     * output bits for turnout control.
181     *
182     * @return  JmriJOptionPane.CLOSED_OPTION if the user cancelled without selecting.
183     *          0 if the user selected PulsedOptionSteady,
184     *          1 if the user selected PulsedOptionPulsed.
185     */
186    public int selectOutputType() {
187        return JmriJOptionPane.showOptionDialog(null,
188                Bundle.getMessage("QuestionPulsedDialog"),
189                Bundle.getMessage("CmriBitsTitle"), JmriJOptionPane.DEFAULT_OPTION,
190                JmriJOptionPane.QUESTION_MESSAGE,
191                null, new String[]{Bundle.getMessage("PulsedOptionSteady"), Bundle.getMessage("PulsedOptionPulsed")},
192                Bundle.getMessage("PulsedOptionSteady"));
193    }
194
195    /**
196     * Public method to notify user when the second bit of a proposed two output
197     * bit turnout has a conflict with another assigned bit.
198     * @param conflict human readable name of turnout with conflict.
199     * @param bitNum conflict bit number.
200     */
201    public void notifySecondBitConflict(String conflict, int bitNum) {
202        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ErrorAssign2Dialog", bitNum, conflict) + "\n" +
203                Bundle.getMessage("ErrorAssignLine2X", Bundle.getMessage("BeanNameTurnout")),
204                Bundle.getMessage("ErrorAssignTitle"),
205                JmriJOptionPane.INFORMATION_MESSAGE );
206    }
207
208    /**
209     * Turnout format is more than a simple format.
210     */
211    @Override
212    public boolean allowMultipleAdditions(@Nonnull String systemName) {
213        return true;
214    }
215
216    /**
217     * {@inheritDoc}
218     */
219    @Override
220    public boolean isNumControlBitsSupported(@Nonnull String systemName) {
221        return true;
222    }
223
224    /**
225     * {@inheritDoc}
226     */
227    @Override
228    public boolean isControlTypeSupported(@Nonnull String systemName) {
229        return true;
230    }
231
232    /**
233     * {@inheritDoc}
234     */
235    @Override
236    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
237        int seperator = 0;
238        String tmpSName;
239
240        if (curAddress.contains(":")) {
241            // Address format passed is in the form node:address
242            seperator = curAddress.indexOf(":");
243            nAddress = Integer.parseInt(curAddress.substring(0, seperator));
244            // check for non-numerical chars
245            try {
246                bitNum = Integer.parseInt(curAddress.substring(seperator + 1));
247            } catch (NumberFormatException ex) {
248                throw new JmriException("Part 2 of " + curAddress + " is not an integer");
249            }
250            tmpSName = getMemo().makeSystemName("T", nAddress, bitNum);
251        } else if (curAddress.contains("B") || (curAddress.contains("b"))) {
252            curAddress = curAddress.toUpperCase();
253            try {
254                //We do this to simply check that we have numbers in the correct places ish
255                Integer.parseInt(curAddress.substring(0, 1));
256                int b = (curAddress.toUpperCase()).indexOf("B") + 1;
257                Integer.parseInt(curAddress.substring(b));
258            } catch (NumberFormatException ex) {
259                throw new JmriException("Unable to convert " + curAddress + " to a valid Hardware Address");
260            }
261            tmpSName = prefix + typeLetter() + curAddress;
262            bitNum = getMemo().getBitFromSystemName(tmpSName);
263            nAddress = getMemo().getNodeAddressFromSystemName(tmpSName);
264        } else {
265            try {
266                // We do this to simply check that the value passed is a number!
267                Integer.parseInt(curAddress);
268            } catch (NumberFormatException ex) {
269                throw new JmriException("Address " + curAddress + " is not an integer");
270            }
271            tmpSName = prefix + "T" + curAddress;
272            bitNum = getMemo().getBitFromSystemName(tmpSName);
273            nAddress = getMemo().getNodeAddressFromSystemName(tmpSName);
274        }
275        return (tmpSName);
276    }
277
278    private int bitNum = 0;
279    private int nAddress = 0;
280
281    /**
282     * {@inheritDoc}
283     */
284    @Override
285    @Nonnull
286    public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) throws NamedBean.BadSystemNameException {
287        systemName = validateSystemNamePrefix(systemName, locale);
288        return getMemo().validateSystemNameFormat(super.validateSystemNameFormat(systemName, locale), typeLetter(), locale);
289    }
290
291    /**
292     * {@inheritDoc}
293     */
294    @Override
295    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
296        return getMemo().validSystemNameFormat(systemName, typeLetter());
297    }
298
299    /**
300     * {@inheritDoc}
301     */
302    @Override
303    public String getEntryToolTip() {
304        return Bundle.getMessage("AddOutputEntryToolTip");
305    }
306
307    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SerialTurnoutManager.class);
308
309}