001package jmri.managers.configurexml;
002
003import java.util.List;
004import java.util.SortedSet;
005import jmri.InstanceManager;
006import jmri.NamedBeanHandle;
007import jmri.Sensor;
008import jmri.Turnout;
009import jmri.TurnoutManager;
010import jmri.TurnoutOperation;
011import jmri.TurnoutOperationManager;
012import jmri.configurexml.TurnoutOperationManagerXml;
013import jmri.configurexml.turnoutoperations.TurnoutOperationXml;
014import org.jdom2.Attribute;
015import org.jdom2.Element;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Provides the abstract base and store functionality for configuring
021 * TurnoutManagers, working with AbstractTurnoutManagers.
022 * <p>
023 * Typically, a subclass will just implement the load(Element turnouts) class,
024 * relying on implementation here to load the individual turnouts. Note that
025 * these are stored explicitly, so the resolution mechanism doesn't need to see
026 * *Xml classes for each specific Turnout or AbstractTurnout subclass at store
027 * time.
028 *
029 * @author Bob Jacobsen Copyright: Copyright (c) 2002
030 */
031public abstract class AbstractTurnoutManagerConfigXML extends AbstractNamedBeanManagerConfigXML {
032
033    public AbstractTurnoutManagerConfigXML() {
034    }
035
036    /**
037     * Default implementation for storing the contents of a TurnoutManager and
038     * associated TurnoutOperations.
039     *
040     * @param o Object to store, of type TurnoutManager
041     * @return Element containing the complete info
042     */
043    @Override
044    public Element store(Object o) {
045        Element turnouts = new Element("turnouts");
046        setStoreElementClass(turnouts);
047        TurnoutManager tm = (TurnoutManager) o;
048        if (tm != null) {
049            TurnoutOperationManagerXml tomx = new TurnoutOperationManagerXml();
050            Element opElem = tomx.store(InstanceManager.getDefault(TurnoutOperationManager.class));
051            turnouts.addContent(opElem);
052            SortedSet<Turnout> tList = tm.getNamedBeanSet();
053            // don't return an element if there are no turnouts to include
054            if (tList.isEmpty()) {
055                return null;
056            }
057            String defaultclosed = tm.getDefaultClosedSpeed();
058            String defaultthrown = tm.getDefaultThrownSpeed();
059            turnouts.addContent(new Element("defaultclosedspeed").addContent(defaultclosed));
060            turnouts.addContent(new Element("defaultthrownspeed").addContent(defaultthrown));
061            for (Turnout t : tList) {
062                // store the turnouts
063                String tName = t.getSystemName();
064                log.debug("system name is {}", tName);
065
066                Element elem = new Element("turnout");
067                elem.addContent(new Element("systemName").addContent(tName));
068                log.debug("store Turnout {}", tName);
069
070                storeCommon(t, elem);
071
072                // include feedback info
073                elem.setAttribute("feedback", t.getFeedbackModeName());
074                NamedBeanHandle<Sensor> s = t.getFirstNamedSensor();
075                if (s != null) {
076                    elem.setAttribute("sensor1", s.getName());
077                }
078                s = t.getSecondNamedSensor();
079                if (s != null) {
080                    elem.setAttribute("sensor2", s.getName());
081                }
082
083                // include turnout inverted
084                elem.setAttribute("inverted", t.getInverted() ? "true" : "false");
085
086                if (t.canLock(Turnout.CABLOCKOUT | Turnout.PUSHBUTTONLOCKOUT)) {
087                    // include turnout locked
088                    elem.setAttribute("locked", t.getLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT) ? "true" : "false");
089                    // include turnout lock mode
090                    String lockOpr;
091                    if (t.canLock(Turnout.CABLOCKOUT) && t.canLock(Turnout.PUSHBUTTONLOCKOUT)) {
092                        lockOpr = "both";
093                    } else if (t.canLock(Turnout.CABLOCKOUT)) {
094                        lockOpr = "cab";
095                    } else if (t.canLock(Turnout.PUSHBUTTONLOCKOUT)) {
096                        lockOpr = "pushbutton";
097                    } else {
098                        lockOpr = "none";
099                    }
100                    elem.setAttribute("lockMode", lockOpr);
101                    // include turnout decoder
102                    elem.setAttribute("decoder", t.getDecoderName());
103                }
104
105                // include number of control bits, if different from one
106                int iNum = t.getNumberControlBits();
107                if (iNum != 1) {
108                    elem.setAttribute("numBits", "" + iNum);
109                }
110
111                // include turnout control type, if different from 0
112                int iType = t.getControlType();
113                if (iType != 0) {
114                    elem.setAttribute("controlType", "" + iType);
115                }
116
117                // add operation stuff
118                String opstr = null;
119                TurnoutOperation op = t.getTurnoutOperation();
120                if (t.getInhibitOperation()) {
121                    opstr = "Off";
122                } else if (op == null) {
123                    opstr = "Default";
124                } else if (op.isNonce()) { // nonce operation appears as subelement
125                    TurnoutOperationXml adapter = TurnoutOperationXml.getAdapter(op);
126                    if (adapter != null) {
127                        Element nonceOpElem = adapter.store(op);
128                        elem.addContent(nonceOpElem);
129                    }
130                } else {
131                    opstr = op.getName();
132                }
133                if (opstr != null) {
134                    elem.setAttribute("automate", opstr);
135                }
136                if ((t.getDivergingSpeed() != null) && (!t.getDivergingSpeed().isEmpty()) && !t.getDivergingSpeed().contains("Global")) {
137                    elem.addContent(new Element("divergingSpeed").addContent(t.getDivergingSpeed()));
138                }
139                if ((t.getStraightSpeed() != null) && (!t.getStraightSpeed().isEmpty()) && !t.getStraightSpeed().contains("Global")) {
140                    elem.addContent(new Element("straightSpeed").addContent(t.getStraightSpeed()));
141                }
142
143                // add element
144                turnouts.addContent(elem);
145            }
146        }
147        return turnouts;
148    }
149
150    /**
151     * Subclass provides implementation to create the correct top element,
152     * including the type information. Default implementation is to use the
153     * local class here.
154     *
155     * @param turnouts The top-level element being created
156     */
157    abstract public void setStoreElementClass(Element turnouts);
158
159    @Override
160    public abstract boolean load(Element shared, Element perNode);
161
162    /**
163     * Utility method to load the individual Turnout objects. If there's no
164     * additional info needed for a specific turnout type, invoke this with the
165     * parent of the set of Turnout elements.
166     *
167     * @param shared Element containing the Turnout elements to load.
168     * @param perNode Element containing per-node Turnout data.
169     * @return true if succeeded
170     */
171    public boolean loadTurnouts(Element shared, Element perNode) {
172        boolean result = true;
173        List<Element> operationList = shared.getChildren("operations");
174        if (operationList.size() > 1) {
175            log.warn("unexpected extra elements found in turnout operations list");
176            result = false;
177        }
178        if (!operationList.isEmpty()) {
179            TurnoutOperationManagerXml tomx = new TurnoutOperationManagerXml();
180            tomx.load(operationList.get(0), null);
181        }
182        List<Element> turnoutList = shared.getChildren("turnout");
183        log.debug("Found {} turnouts", turnoutList.size());
184        TurnoutManager tm = InstanceManager.turnoutManagerInstance();
185        tm.setPropertyChangesSilenced("beans", true);
186
187        try {
188            if (shared.getChild("defaultclosedspeed") != null) {
189                String closedSpeed = shared.getChild("defaultclosedspeed").getText();
190                if (closedSpeed != null && !closedSpeed.isEmpty()) {
191                    tm.setDefaultClosedSpeed(closedSpeed);
192                }
193            }
194        } catch (jmri.JmriException ex) {
195            log.error("JmriException {}", ex.getMessage() );
196        }
197
198        try {
199            if (shared.getChild("defaultthrownspeed") != null) {
200                String thrownSpeed = shared.getChild("defaultthrownspeed").getText();
201                if (thrownSpeed != null && !thrownSpeed.isEmpty()) {
202                    tm.setDefaultThrownSpeed(thrownSpeed);
203                }
204            }
205        } catch (jmri.JmriException ex) {
206            log.error("JmriException {}", ex.getMessage() );
207        }
208
209        for (Element elem : turnoutList) {
210            String sysName = getSystemName(elem);
211            if (sysName == null) {
212                log.error("unexpected null in systemName {}", elem);
213                result = false;
214                break;
215            }
216            String userName = getUserName(elem);
217
218            checkNameNormalization(sysName, userName, tm);
219
220            log.debug("create turnout: ({})({})", sysName, (userName == null ? "<null>" : userName));
221            Turnout t = tm.getBySystemName(sysName);
222            if (t == null) {
223                t = tm.newTurnout(sysName, userName);
224                // nothing is logged in the console window as the newTurnoutFunction already does this.
225            } else if (userName != null) {
226                t.setUserName(userName);
227            }
228
229            // Load common parts
230            loadCommon(t, elem);
231
232            // now configure feedback if needed
233            Attribute a;
234            a = elem.getAttribute("sensor1");
235            if (a != null) {
236                try {
237                    t.provideFirstFeedbackSensor(a.getValue());
238                } catch (jmri.JmriException e) {
239                    result = false;
240                }
241            }
242            a = elem.getAttribute("sensor2");
243            if (a != null) {
244                try {
245                    t.provideSecondFeedbackSensor(a.getValue());
246                } catch (jmri.JmriException e) {
247                    result = false;
248                }
249            }
250            a = elem.getAttribute("feedback");
251            if (a != null) {
252                try {
253                    t.setFeedbackMode(a.getValue());
254                } catch (IllegalArgumentException e) {
255                    log.error("Can not set feedback mode: '{}' for turnout: '{}' user name: '{}'",
256                            a.getValue(), sysName, (userName == null ? "" : userName));
257                    result = false;
258                }
259            }
260
261            // check for turnout inverted
262            t.setInverted(getAttributeBool(elem, "inverted", false));
263
264            // check for turnout decoder
265            a = elem.getAttribute("decoder");
266            if (a != null) {
267                t.setDecoderName(a.getValue());
268            }
269
270            // check for turnout lock mode
271            a = elem.getAttribute("lockMode");
272            if (a != null) {
273                if (a.getValue().equals("both")) {
274                    t.enableLockOperation(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true);
275                }
276                if (a.getValue().equals("cab")) {
277                    t.enableLockOperation(Turnout.CABLOCKOUT, true);
278                    t.enableLockOperation(Turnout.PUSHBUTTONLOCKOUT, false);
279                }
280                if (a.getValue().equals("pushbutton")) {
281                    t.enableLockOperation(Turnout.PUSHBUTTONLOCKOUT, true);
282                    t.enableLockOperation(Turnout.CABLOCKOUT, false);
283                }
284            }
285
286            // check for turnout locked
287            a = elem.getAttribute("locked");
288            if (a != null) {
289                t.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, a.getValue().equals("true"));
290            }
291
292            // number of bits, if present - if not, defaults to 1
293            a = elem.getAttribute("numBits");
294            if (a == null) {
295                t.setNumberControlBits(1);
296            } else {
297                int iNum = Integer.parseInt(a.getValue());
298                if ((iNum == 1) || (iNum == 2)) {
299                    t.setNumberControlBits(iNum);
300                } else {
301                    log.warn("illegal number of output bits for control of turnout {}", sysName);
302                    t.setNumberControlBits(1);
303                    result = false;
304                }
305            }
306
307            // control type, if present - if not, defaults to 0
308            a = elem.getAttribute("controlType");
309            if (a == null) {
310                t.setControlType(0);
311            } else {
312                int iType = Integer.parseInt(a.getValue());
313                if (iType >= 0) {
314                    t.setControlType(iType);
315                } else {
316                    log.warn("illegal control type for control of turnout {}", sysName);
317                    t.setControlType(0);
318                    result = false;
319                }
320            }
321
322            // operation stuff
323            List<Element> myOpList = elem.getChildren("operation");
324            if (!myOpList.isEmpty()) {
325                if (myOpList.size() > 1) {
326                    log.warn("unexpected extra elements found in turnout-specific operations");
327                    result = false;
328                }
329                TurnoutOperation toper = TurnoutOperationXml.loadOperation(myOpList.get(0));
330                t.setTurnoutOperation(toper);
331            } else {
332                a = elem.getAttribute("automate");
333                if (a != null) {
334                    String str = a.getValue();
335                    if (str.equals("Off")) {
336                        t.setInhibitOperation(true);
337                    } else if (!str.equals("Default")) {
338                        t.setInhibitOperation(false);
339                        TurnoutOperation toper
340                                = InstanceManager.getDefault(TurnoutOperationManager.class).getOperation(str);
341                        t.setTurnoutOperation(toper);
342                    } else {
343                        t.setInhibitOperation(false);
344                    }
345                }
346            }
347
348            //  set initial state from sensor feedback if appropriate
349            t.setInitialKnownStateFromFeedback();
350            try {
351                t.setDivergingSpeed("Global");
352                if (elem.getChild("divergingSpeed") != null) {
353                    String speed = elem.getChild("divergingSpeed").getText();
354                    if (speed != null && !speed.isEmpty() && !speed.contains("Global")) {
355                        t.setDivergingSpeed(speed);
356                    }
357                }
358            } catch (jmri.JmriException ex) {
359                log.error("Turnout {} : {}", t, ex.getMessage());
360            }
361
362            try {
363                t.setStraightSpeed("Global");
364                if (elem.getChild("straightSpeed") != null) {
365                    String speed = elem.getChild("straightSpeed").getText();
366                    if (speed != null && !speed.isEmpty() && !speed.contains("Global")) {
367                        t.setStraightSpeed(speed);
368                    }
369                }
370            } catch (jmri.JmriException ex) {
371                log.error("Turnout {} : {}", t, ex.getMessage());
372            }
373        }
374
375        tm.setPropertyChangesSilenced("beans", false);
376
377        return result;
378    }
379
380    @Override
381    public int loadOrder() {
382        return InstanceManager.turnoutManagerInstance().getXMLOrder();
383    }
384
385    private final static Logger log = LoggerFactory.getLogger(AbstractTurnoutManagerConfigXML.class);
386
387}