001package jmri.jmrit.logix.configurexml;
002
003import java.util.List;
004import java.util.SortedSet;
005//import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
006
007import jmri.DccLocoAddress;
008import jmri.InstanceManager;
009import jmri.SpeedStepMode;
010import jmri.jmrit.logix.BlockOrder;
011import jmri.jmrit.logix.OBlock;
012import jmri.jmrit.logix.SCWarrant;
013import jmri.jmrit.logix.SpeedUtil;
014import jmri.jmrit.logix.ThrottleSetting;
015import jmri.jmrit.logix.ThrottleSetting.Command;
016import jmri.jmrit.logix.ThrottleSetting.CommandValue;
017import jmri.jmrit.logix.ThrottleSetting.ValueType;
018import jmri.jmrit.logix.Warrant;
019import jmri.jmrit.logix.WarrantManager;
020import org.jdom2.Attribute;
021import org.jdom2.DataConversionException;
022import org.jdom2.Element;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * Provides the abstract base and store functionality for
028 * configuring the WarrantManager.
029 * <p>
030 * Typically, a subclass will just implement the load(Element warrant)
031 * class, relying on implementation here to load the individual Warrant objects.
032 *
033 * @author Pete Cressman Copyright: Copyright (c) 2009
034 */
035public class WarrantManagerXml extends jmri.configurexml.AbstractXmlAdapter {
036
037    public WarrantManagerXml() {
038    }
039
040    /**
041     * Store the contents of a WarrantManager.
042     *
043     * @param o Object to store, of type warrantManager
044     * @return Element containing the complete info
045     */
046    @Override
047    public Element store(Object o) {
048        Element warrants = new Element("warrants");
049        warrants.setAttribute("class", "jmri.jmrit.logix.configurexml.WarrantManagerXml");
050        WarrantManager wm = (WarrantManager) o;
051        if (wm != null) {
052            SortedSet<Warrant> warrantList = wm.getNamedBeanSet();
053            // don't return an element if there are no warrants to include
054            if (warrantList.isEmpty()) {
055                return null;
056            }
057            for (Warrant warrant : warrantList) {
058                String sName = warrant.getSystemName();
059                String uName = warrant.getUserName();
060                log.debug("Warrant: sysName= {}, userName= {}", sName, uName);
061                Element elem = new Element("warrant");
062                elem.setAttribute("systemName", sName);
063                if (uName == null) {
064                    uName = "";
065                }
066                if (uName.length() > 0) {
067                    elem.setAttribute("userName", uName);
068                }
069                if (warrant instanceof SCWarrant) {
070                    elem.setAttribute("wtype", "SC");
071                    elem.setAttribute("speedFactor", "" + ((SCWarrant) warrant).getSpeedFactor());
072                    elem.setAttribute("timeToPlatform", "" + ((SCWarrant) warrant).getTimeToPlatform());
073                    elem.setAttribute("forward", ((SCWarrant) warrant).getForward() ? "true" : "false");
074                } else {
075                    elem.setAttribute("wtype", "normal");
076                }
077                String comment = warrant.getComment();
078                if (comment != null) {
079                    Element c = new Element("comment");
080                    c.addContent(comment);
081                    elem.addContent(c);
082                }
083
084                List<BlockOrder> orders = warrant.getBlockOrders();
085                if (orders == null) {
086                    log.error("Warrant {} has no Route defined. (no BlockOrders) Cannot store.", warrant.getDisplayName());
087                    continue;
088                }
089                for (BlockOrder bo : orders) {
090                    elem.addContent(storeOrder(bo, "blockOrder"));
091                }
092
093                BlockOrder viaOrder = warrant.getViaOrder();
094                if (viaOrder != null) {
095                    elem.addContent(storeOrder(viaOrder, "viaOrder"));
096                }
097                BlockOrder avoidOrder = warrant.getAvoidOrder();
098                if (avoidOrder != null) {
099                    elem.addContent(storeOrder(avoidOrder, "avoidOrder"));
100                }
101
102                List<ThrottleSetting> throttleCmds = warrant.getThrottleCommands();
103                for (ThrottleSetting ts : throttleCmds) {
104                    elem.addContent(storeThrottleSetting(ts));
105                }
106
107                elem.addContent(storeTrain(warrant, "train"));
108
109                // and put this element out
110                warrants.addContent(elem);
111            }
112        }
113        return warrants;
114    }
115
116    private static Element storeTrain(Warrant warrant, String type) {
117        Element elem = new Element(type);
118        SpeedUtil speedUtil = warrant.getSpeedUtil();
119        String str = speedUtil.getRosterId();
120        if (str==null) {
121            str = "";
122        }
123        elem.setAttribute("trainId", str);
124
125        DccLocoAddress addr = speedUtil.getDccAddress();
126        if (addr != null) {
127            elem.setAttribute("dccAddress", ""+addr.getNumber());
128            elem.setAttribute("dccType", ""+addr.getProtocol().getShortName());
129        }
130        elem.setAttribute("runBlind", warrant.getRunBlind()?"true":"false");
131        elem.setAttribute("shareRoute", warrant.getShareRoute()?"true":"false");
132        elem.setAttribute("noRamp", warrant.getNoRamp()?"true":"false");
133
134        str = warrant.getTrainName();
135        if (str==null) {
136            str = "";
137        }
138        elem.setAttribute("trainName", str);
139
140        return elem;
141    }
142
143    private static Element storeOrder(BlockOrder order, String type) {
144        Element elem = new Element(type);
145        OBlock block = order.getBlock();
146        if (block!=null) {
147            Element blk = new Element("block");
148            blk.setAttribute("systemName", block.getSystemName());
149            String uname = block.getUserName();
150            if (uname==null) uname = "";
151            if (uname.length()>0) {
152                blk.setAttribute("userName", uname);
153            }
154            elem.addContent(blk);
155        } else {
156            log.error("Null block in BlockOrder!");
157        }
158        String str = order.getPathName();
159        if (str == null) {
160            str = "";
161        }
162        elem.setAttribute("pathName", str);
163
164        str = order.getEntryName();
165        if (str == null) {
166            str = "";
167        }
168        elem.setAttribute("entryName", str);
169
170        str = order.getExitName();
171        if (str == null) {
172            str = "";
173        }
174        elem.setAttribute("exitName", str);
175
176        return elem;
177    }
178    private static Element storeThrottleSetting(ThrottleSetting ts) {
179        Element element = new Element("throttleSetting");
180        element.setAttribute("elapsedTime", String.valueOf(ts.getTime()));
181        String name = ts.getBeanSystemName();
182        if (name != null) {
183            element.setAttribute("beanName", name);
184        } else {
185            element.setAttribute("beanName", "");
186        }
187        element.setAttribute("trackSpeed", String.valueOf(ts.getTrackSpeed()));
188
189        Element elem = new Element("command");
190        Command cmd = ts.getCommand();
191        elem.setAttribute("commandType", String.valueOf(cmd.getIntId()));
192        elem.setAttribute("fKey", String.valueOf(ts.getKeyNum()));
193        element.addContent(elem);
194
195        elem = new Element("commandValue");
196        CommandValue cmdVal = ts.getValue();
197        elem.setAttribute("valueType", String.valueOf(cmdVal.getType().getIntId()));
198        elem.setAttribute("speedMode", cmdVal.getMode().name);
199        elem.setAttribute("floatValue", String.valueOf(cmdVal.getFloat()));
200        if (!cmdVal.getText().isEmpty()) {
201            elem.setAttribute("textValue", cmdVal.getText());
202        }
203        element.addContent(elem);
204
205        return element;
206    }
207
208    @Override
209    public boolean load(Element shared, Element perNode) {
210
211        WarrantManager manager = InstanceManager.getDefault(WarrantManager.class);
212
213        if (shared.getChildren().isEmpty()) {
214            return true;
215        }
216
217        List<Element> warrantList = shared.getChildren("warrant");
218        log.debug("Found {} Warrant objects", warrantList.size());
219        boolean previouslyLoaded = false;
220        for (Element elem : warrantList) {
221            if (elem.getAttribute("systemName") == null) {
222                log.warn("unexpected null for systemName in elem {}", elem);
223                break;
224            }
225            String sysName = null;
226            if (elem.getAttribute("systemName") != null)
227                sysName = elem.getAttribute("systemName").getValue();
228
229            String userName = null;
230            if (elem.getAttribute("userName") != null)
231                userName = elem.getAttribute("userName").getValue();
232
233            boolean SCWa = true;
234            log.debug("loading warrant {}", sysName);
235            Attribute wType = elem.getAttribute("wtype");
236            if (wType == null) {
237                log.debug("wtype is null for {}", sysName);
238                SCWa = false;
239            } else if (!wType.getValue().equals("SC")) {
240                log.debug("wtype is {} for {}", wType.getValue(), sysName);
241                SCWa = false;
242            }
243
244            long timeToPlatform = 500;
245            Attribute TTP = elem.getAttribute("timeToPlatform");
246            if (TTP != null) {
247                try {
248                    timeToPlatform = TTP.getLongValue();
249                } catch (DataConversionException e) {
250                    log.debug("ignoring DataConversionException (and reverting to default value): {}", e.toString());
251                }
252            }
253
254            Warrant warrant = manager.createNewWarrant(sysName, userName, SCWa, timeToPlatform);
255            if (warrant == null) {
256                log.info("Warrant \"{}\" (userName={}) previously loaded. This version not loaded.", sysName, userName);
257                previouslyLoaded = true;
258                continue;
259            }
260            previouslyLoaded = false;
261            if (SCWa && warrant instanceof SCWarrant) {
262                if (elem.getAttribute("forward") != null) {
263                    ((SCWarrant)warrant).setForward(elem.getAttribute("forward").getValue().equals("true"));
264                }
265                if (elem.getAttribute("speedFactor") != null) {
266                    try {
267                        ((SCWarrant)warrant).setSpeedFactor(elem.getAttribute("speedFactor").getFloatValue());
268                    } catch (DataConversionException e) {
269                        log.warn("error converting speed value");
270                    }
271                }
272                warrant.setNoRamp(SCWa);
273                warrant.setShareRoute(SCWa);
274            }
275            List<Element> orders = elem.getChildren("blockOrder");
276            int count = 0;
277            for (Element ord : orders) {
278                BlockOrder bo = loadBlockOrder(ord);
279                if (bo == null) {
280                    log.error("Bad BlockOrder in warrant \"{}\" elem= {}.", warrant.getDisplayName(), elem.getText());
281                } else {
282                    bo.setIndex(count++);
283                    warrant.addBlockOrder(bo);
284                }
285            }
286            String c = elem.getChildText("comment");
287            if (c != null) {
288                warrant.setComment(c);
289            }
290
291            Element order = elem.getChild("viaOrder");
292            if (order!=null) {
293                warrant.setViaOrder(loadBlockOrder(order));
294            }
295            order = elem.getChild("avoidOrder");
296            if (order!=null) {
297                warrant.setAvoidOrder(loadBlockOrder(order));
298            }
299
300            if (SCWa) {
301                boolean forward =true;
302                if (elem.getAttribute("forward") != null) {
303                    forward = elem.getAttribute("forward").getValue().equals("true");
304                }
305                if (warrant instanceof SCWarrant) {
306                    ((SCWarrant)warrant).setForward(forward);
307                }
308                warrant.setNoRamp(SCWa);
309                warrant.setShareRoute(SCWa);
310            }
311            Element train = elem.getChild("train");
312            if (train!=null) {
313                loadTrain(train, warrant);
314            }
315        }
316        if (!previouslyLoaded) {
317            // A second pass through the warrant list done to load the commands. This is done so that
318            // references made to warrants in commands are fully specified. Due to ThrottleSetting
319            // Ctor using provideWarrant to establish the referenced warrant.
320            warrantList = shared.getChildren("warrant");
321            for (Element elem : warrantList) {
322                // boolean forward = true;  // variable not used, see GitHub JMRI/JMRI Issue #5661
323                if (elem.getAttribute("systemName") == null) {
324                    break;
325                }
326                if (elem.getAttribute("systemName") != null) {
327                    String sysName = elem.getAttribute("systemName").getValue();
328                    if (sysName != null) {
329                        Warrant warrant = manager.getBySystemName(sysName);
330                        List<Element> throttleCmds;
331                        if (warrant != null) {
332                            log.debug("warrant: {}", warrant.getDisplayName());
333                            throttleCmds = elem.getChildren("throttleCommand");
334                            if (throttleCmds != null && !throttleCmds.isEmpty()) {
335                                log.debug("throttleCommand size= {}",throttleCmds.size());
336                                throttleCmds.forEach((e) -> {
337                                    warrant.addThrottleCommand(loadThrottleCommand(e, warrant));
338                                });
339                                warrant.setTrackSpeeds();
340                            } else {
341                                throttleCmds = elem.getChildren("throttleSetting");
342                                if (throttleCmds != null) {
343                                    log.debug("throttleSetting size= {}",throttleCmds.size());
344                                    throttleCmds.forEach((e) -> {
345                                        warrant.addThrottleCommand(loadThrottleSetting(e, warrant));
346                                    });
347                                }
348                            }
349                        }
350                    }
351                }
352            }
353        }
354        return true;
355    }
356
357    private static void loadTrain(Element elem, Warrant warrant) {
358        SpeedUtil speedUtil = warrant.getSpeedUtil();
359        // if a RosterEntry exists "trainId" will be the Roster Id, otherwise a train name
360        if (elem.getAttribute("trainId") != null) {
361            speedUtil.setRosterId(elem.getAttribute("trainId").getValue());
362        }
363        if (speedUtil.getRosterEntry() == null) {
364            if (elem.getAttribute("dccAddress") != null) {
365                try {
366                   int address = elem.getAttribute("dccAddress").getIntValue();
367                   String type = "L";
368                   if (elem.getAttribute("dccType") != null) {
369                       type = elem.getAttribute("dccType").getValue();
370                   }
371                   if (!speedUtil.setDccAddress(address, type)) {
372                       log.error("dccAddress {}, dccType {} in Warrant {}",
373                               address, type, warrant.getDisplayName());
374                   }
375                } catch (org.jdom2.DataConversionException dce) {
376                    log.error("{} for dccAddress in Warrant {}", dce, warrant.getDisplayName());
377                }
378            }
379        }
380        if (elem.getAttribute("runBlind") != null) {
381            warrant.setRunBlind(elem.getAttribute("runBlind").getValue().equals("true"));
382        }
383        if (elem.getAttribute("shareRoute") != null) {
384            warrant.setShareRoute(elem.getAttribute("shareRoute").getValue().equals("true"));
385        }
386        if (elem.getAttribute("noRamp") != null) {
387            warrant.setNoRamp(elem.getAttribute("noRamp").getValue().equals("true"));
388        }
389        if (elem.getAttribute("trainName") != null) {
390            warrant.setTrainName(elem.getAttribute("trainName").getValue());
391        }
392    }
393
394    private static BlockOrder loadBlockOrder(Element elem) {
395
396        OBlock block = null;
397        List<Element> blocks = elem.getChildren("block");
398        if (blocks.size()>1) log.error("More than one block present: {}", blocks.size());
399        if (blocks.size()>0) {
400            String name = blocks.get(0).getAttribute("systemName").getValue();
401            block = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(name);
402            if (block == null) {
403                log.error("No such Block \"{}\" found.", name);
404                return null;
405            }
406            if (log.isDebugEnabled()) log.debug("Load Block {}.", name);
407        } else {
408            log.error("Null BlockOrder element");
409            return null;
410        }
411        Attribute attr = elem.getAttribute("pathName");
412        String pathName = null;
413        if (attr != null) {
414            pathName = attr.getValue();
415        }
416
417        attr = elem.getAttribute("entryName");
418        String entryName = null;
419        if (attr != null)
420            entryName =attr.getValue();
421
422        attr = elem.getAttribute("exitName");
423        String exitName = null;
424        if (attr != null)
425            exitName =attr.getValue();
426
427        return new BlockOrder(block, pathName, entryName, exitName);
428    }
429
430    private static ThrottleSetting loadThrottleSetting(Element element, Warrant w) {
431
432        ThrottleSetting ts = new ThrottleSetting();
433
434        Attribute attr = element.getAttribute("elapsedTime");
435        if (attr != null) {
436            ts.setTime(Long.parseLong(attr.getValue()));
437        }
438
439        Command cmd = null;
440        Element elem = element.getChild("command");
441        if (elem != null) {
442            attr = elem.getAttribute("commandType");
443            if (attr != null) {
444                try {
445                    cmd = ThrottleSetting.getCommandTypeFromInt(Integer.parseInt(attr.getValue()));
446                    ts.setCommand(cmd);
447                } catch (IllegalArgumentException iae) {
448                    log.error("{} for {} in warrant {}",iae.getMessage(), ts.toString(), w.getDisplayName());
449                }
450            } else {
451                log.error("Command type is null for {} in warrant {}", ts.toString(), w.getDisplayName());
452            }
453            attr = elem.getAttribute("fKey");
454            if (attr != null) {
455                ts.setKeyNum(Integer.parseInt(attr.getValue()));
456            }
457        }
458
459        elem = element.getChild("commandValue");
460        ValueType valType = null;
461        SpeedStepMode mode = null;
462        float floatVal = 0;
463        String textVal = "";
464        if (elem != null) {
465            attr = elem.getAttribute("valueType");
466            if (attr != null) {
467                try {
468                    valType = ThrottleSetting.getValueTypeFromInt(Integer.parseInt(attr.getValue()));
469                } catch (IllegalArgumentException iae) {
470                    log.error("{} for throttleSetting {} in warrant {}",iae.getMessage(), ts.toString(), w.getDisplayName());
471                }
472            } else {
473                log.error("Value type is null for {} in warrant {}", ts.toString(), w.getDisplayName());
474            }
475            attr = elem.getAttribute("speedMode");
476            if (attr != null) {
477                mode = SpeedStepMode.getByName(attr.getValue());
478            }
479            attr = elem.getAttribute("floatValue");
480            if (attr != null) {
481                floatVal = Float.parseFloat(attr.getValue());
482            }
483
484            attr = elem.getAttribute("textValue");
485            if (attr != null) {
486                textVal = attr.getValue();
487            }
488        }
489        ts.setValue(valType, mode, floatVal, textVal);
490
491        attr = element.getAttribute("trackSpeed");
492        if (attr != null) {
493            ts.setTrackSpeed(Float.parseFloat(attr.getValue()));
494        }
495
496        attr = element.getAttribute("beanName");
497        if (attr != null) {
498            String errMsg = ts.setNamedBean(cmd, attr.getValue());
499            if (errMsg != null) {
500                log.error("{} for {} in warrant {}", errMsg, ts.toString(), w.getDisplayName());
501            }
502        }
503        return ts;
504    }
505
506    // pre 4.21.3
507//    @SuppressFBWarnings(value="NP_LOAD_OF_KNOWN_NULL_VALUE", justification="nothing wrong about a null return")
508    private static ThrottleSetting loadThrottleCommand(Element elem, Warrant w) {
509        long time = 0;
510        try {
511            time = elem.getAttribute("time").getLongValue();
512        } catch (org.jdom2.DataConversionException dce) {
513            log.warn("error loading throttle command");
514        }
515
516        Attribute attr = elem.getAttribute("command");
517        String command = null;
518        if (attr != null) {
519            command = attr.getValue();
520        } else {
521            log.error("Command type is null. ThrottleSetting not loaded for warrant {}", w.getDisplayName());
522            return null;
523        }
524
525        attr = elem.getAttribute("value");
526        String value = null;
527        if (attr != null)
528            value =attr.getValue();
529
530        attr = elem.getAttribute("block");
531        String block = null;
532        if (attr != null)
533            block =attr.getValue();
534
535        float speed = 0.0f;
536        attr = elem.getAttribute("trackSpeed");
537        if (attr != null) {
538            try {
539                speed = attr.getFloatValue();
540            } catch (DataConversionException ex) {
541                speed = 0.0f;
542                log.error("Unable to read speed of command.", ex);
543            }
544        }
545
546        return new ThrottleSetting(time, command, value, block, speed);
547    }
548
549    @Override
550    public int loadOrder(){
551        return InstanceManager.getDefault(WarrantManager.class).getXMLOrder();
552    }
553
554    private final static Logger log = LoggerFactory.getLogger(WarrantManagerXml.class);
555
556}