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        element.addContent(elem);
201
202        return element;
203    }
204
205    @Override
206    public boolean load(Element shared, Element perNode) {
207
208        WarrantManager manager = InstanceManager.getDefault(WarrantManager.class);
209        
210        if (shared.getChildren().isEmpty()) {
211            return true;
212        }
213        
214        List<Element> warrantList = shared.getChildren("warrant");
215        log.debug("Found {} Warrant objects", warrantList.size());
216        boolean previouslyLoaded = false;
217        for (Element elem : warrantList) {
218            if (elem.getAttribute("systemName") == null) {
219                log.warn("unexpected null for systemName in elem {}", elem);
220                break;
221            }
222            String sysName = null;
223            if (elem.getAttribute("systemName") != null)
224                sysName = elem.getAttribute("systemName").getValue();
225
226            String userName = null;
227            if (elem.getAttribute("userName") != null)
228                userName = elem.getAttribute("userName").getValue();
229            
230            boolean SCWa = true;
231            log.debug("loading warrant {}", sysName);
232            Attribute wType = elem.getAttribute("wtype");
233            if (wType == null) {
234                log.debug("wtype is null for {}", sysName);
235                SCWa = false;
236            } else if (!wType.getValue().equals("SC")) {
237                log.debug("wtype is {} for {}", wType.getValue(), sysName);
238                SCWa = false;
239            }
240            
241            long timeToPlatform = 500;
242            Attribute TTP = elem.getAttribute("timeToPlatform");
243            if (TTP != null) {
244                try {
245                    timeToPlatform = TTP.getLongValue();
246                } catch (DataConversionException e) {
247                    log.debug("ignoring DataConversionException (and reverting to default value): {}", e.toString());
248                }
249            }
250
251            Warrant warrant = manager.createNewWarrant(sysName, userName, SCWa, timeToPlatform);
252            if (warrant == null) {
253                log.info("Warrant \"{}\" (userName={}) previously loaded. This version not loaded.", sysName, userName);
254                previouslyLoaded = true;
255                continue;
256            }
257            previouslyLoaded = false;
258            if (SCWa && warrant instanceof SCWarrant) {
259                if (elem.getAttribute("forward") != null) {
260                    ((SCWarrant)warrant).setForward(elem.getAttribute("forward").getValue().equals("true"));
261                }
262                if (elem.getAttribute("speedFactor") != null) {
263                    try {
264                        ((SCWarrant)warrant).setSpeedFactor(elem.getAttribute("speedFactor").getFloatValue());
265                    } catch (DataConversionException e) {
266                        log.warn("error converting speed value");
267                    }
268                }
269                warrant.setNoRamp(SCWa);
270                warrant.setShareRoute(SCWa);
271            }
272            List<Element> orders = elem.getChildren("blockOrder");
273            int count = 0;
274            for (Element ord : orders) {
275                BlockOrder bo = loadBlockOrder(ord);
276                if (bo == null) {
277                    log.error("Bad BlockOrder in warrant \"{}\" elem= {}.", warrant.getDisplayName(), elem.getText());
278                } else {
279                    bo.setIndex(count++);
280                    warrant.addBlockOrder(bo);
281                }
282            }
283            String c = elem.getChildText("comment");
284            if (c != null) {
285                warrant.setComment(c);
286            }
287            
288            Element order = elem.getChild("viaOrder");
289            if (order!=null) {
290                warrant.setViaOrder(loadBlockOrder(order));             
291            }
292            order = elem.getChild("avoidOrder");
293            if (order!=null) {
294                warrant.setAvoidOrder(loadBlockOrder(order));               
295            }
296
297            if (SCWa) {
298                boolean forward =true;
299                if (elem.getAttribute("forward") != null) {
300                    forward = elem.getAttribute("forward").getValue().equals("true");
301                }
302                if (warrant instanceof SCWarrant) {
303                    ((SCWarrant)warrant).setForward(forward);
304                }
305                warrant.setNoRamp(SCWa);
306                warrant.setShareRoute(SCWa);
307            }
308            Element train = elem.getChild("train");
309            if (train!=null) {
310                loadTrain(train, warrant);
311            }
312        }
313        if (!previouslyLoaded) {
314            // A second pass through the warrant list done to load the commands. This is done so that
315            // references made to warrants in commands are fully specified. Due to ThrottleSetting
316            // Ctor using provideWarrant to establish the referenced warrant.
317            warrantList = shared.getChildren("warrant");
318            for (Element elem : warrantList) {
319                // boolean forward = true;  // variable not used, see GitHub JMRI/JMRI Issue #5661
320                if (elem.getAttribute("systemName") == null) {
321                    break;
322                }
323                if (elem.getAttribute("systemName") != null) {
324                    String sysName = elem.getAttribute("systemName").getValue();
325                    if (sysName != null) {
326                        Warrant warrant = manager.getBySystemName(sysName);
327                        List<Element> throttleCmds;
328                        if (warrant != null) {
329                            log.debug("warrant: {}", warrant.getDisplayName());
330                            throttleCmds = elem.getChildren("throttleCommand");
331                            if (throttleCmds != null && !throttleCmds.isEmpty()) {
332                                log.debug("throttleCommand size= {}",throttleCmds.size());
333                                throttleCmds.forEach((e) -> {
334                                    warrant.addThrottleCommand(loadThrottleCommand(e, warrant));
335                                });
336                                warrant.setTrackSpeeds();
337                            } else {
338                                throttleCmds = elem.getChildren("throttleSetting");
339                                if (throttleCmds != null) {
340                                    log.debug("throttleSetting size= {}",throttleCmds.size());
341                                    throttleCmds.forEach((e) -> {
342                                        warrant.addThrottleCommand(loadThrottleSetting(e, warrant));
343                                    });
344                                }
345                            }
346                        }
347                    }
348                }
349            }
350        }
351        return true;
352    }
353
354    private static void loadTrain(Element elem, Warrant warrant) {
355        SpeedUtil speedUtil = warrant.getSpeedUtil();
356        // if a RosterEntry exists "trainId" will be the Roster Id, otherwise a train name
357        if (elem.getAttribute("trainId") != null) {
358            speedUtil.setRosterId(elem.getAttribute("trainId").getValue());
359        }
360        if (speedUtil.getRosterEntry() == null) {
361            if (elem.getAttribute("dccAddress") != null) {
362                try {
363                   int address = elem.getAttribute("dccAddress").getIntValue();
364                   String type = "L";
365                   if (elem.getAttribute("dccType") != null) {
366                       type = elem.getAttribute("dccType").getValue();
367                   }
368                   if (!speedUtil.setDccAddress(address, type)) {
369                       log.error("dccAddress {}, dccType {} in Warrant {}",
370                               address, type, warrant.getDisplayName());
371                   }
372                } catch (org.jdom2.DataConversionException dce) {
373                    log.error("{} for dccAddress in Warrant {}", dce, warrant.getDisplayName());
374                }
375            }
376        }
377        if (elem.getAttribute("runBlind") != null) {
378            warrant.setRunBlind(elem.getAttribute("runBlind").getValue().equals("true"));
379        }
380        if (elem.getAttribute("shareRoute") != null) {
381            warrant.setShareRoute(elem.getAttribute("shareRoute").getValue().equals("true"));
382        }
383        if (elem.getAttribute("noRamp") != null) {
384            warrant.setNoRamp(elem.getAttribute("noRamp").getValue().equals("true"));
385        }
386        if (elem.getAttribute("trainName") != null) {
387            warrant.setTrainName(elem.getAttribute("trainName").getValue());
388        }
389    }
390
391    private static BlockOrder loadBlockOrder(Element elem) {
392
393        OBlock block = null;
394        List<Element> blocks = elem.getChildren("block");
395        if (blocks.size()>1) log.error("More than one block present: {}", blocks.size());
396        if (blocks.size()>0) {
397            String name = blocks.get(0).getAttribute("systemName").getValue();
398            block = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(name);
399            if (block == null) {
400                log.error("No such Block \"{}\" found.", name);
401                return null;
402            }
403            if (log.isDebugEnabled()) log.debug("Load Block {}.", name);
404        } else {
405            log.error("Null BlockOrder element");
406            return null;
407        }
408        Attribute attr = elem.getAttribute("pathName");
409        String pathName = null;
410        if (attr != null) {
411            pathName = attr.getValue();
412        }
413
414        attr = elem.getAttribute("entryName");
415        String entryName = null;
416        if (attr != null)
417            entryName =attr.getValue();
418
419        attr = elem.getAttribute("exitName");
420        String exitName = null;
421        if (attr != null)
422            exitName =attr.getValue();
423
424        return new BlockOrder(block, pathName, entryName, exitName);
425    }
426
427    private static ThrottleSetting loadThrottleSetting(Element element, Warrant w) {
428
429        ThrottleSetting ts = new ThrottleSetting();
430
431        Attribute attr = element.getAttribute("elapsedTime");
432        if (attr != null) {
433            ts.setTime(Long.parseLong(attr.getValue()));
434        }
435
436        Command cmd = null;
437        Element elem = element.getChild("command");
438        if (elem != null) {
439            attr = elem.getAttribute("commandType");
440            if (attr != null) {
441                try {
442                    cmd = ThrottleSetting.getCommandTypeFromInt(Integer.parseInt(attr.getValue()));
443                    ts.setCommand(cmd);
444                } catch (IllegalArgumentException iae) {
445                    log.error("{} for {} in warrant {}",iae.getMessage(), ts.toString(), w.getDisplayName());
446                }
447            } else {
448                log.error("Command type is null for {} in warrant {}", ts.toString(), w.getDisplayName());
449            }
450            attr = elem.getAttribute("fKey");
451            if (attr != null) {
452                ts.setKeyNum(Integer.parseInt(attr.getValue()));
453            }
454        }
455
456        elem = element.getChild("commandValue");
457        ValueType valType = null;
458        SpeedStepMode mode = null;
459        float floatVal = 0;
460        if (elem != null) {
461            attr = elem.getAttribute("valueType");
462            if (attr != null) {
463                try {                
464                    valType = ThrottleSetting.getValueTypeFromInt(Integer.parseInt(attr.getValue()));
465                } catch (IllegalArgumentException iae) {
466                    log.error("{} for throttleSetting {} in warrant {}",iae.getMessage(), ts.toString(), w.getDisplayName());
467                }
468            } else {
469                log.error("Value type is null for {} in warrant {}", ts.toString(), w.getDisplayName());
470            }
471            attr = elem.getAttribute("speedMode");
472            if (attr != null) {
473                mode = SpeedStepMode.getByName(attr.getValue());
474            }
475            attr = elem.getAttribute("floatValue");
476            if (attr != null) {
477                floatVal = Float.parseFloat(attr.getValue());
478            }
479        }
480        ts.setValue(valType, mode, floatVal);
481
482        attr = element.getAttribute("trackSpeed");
483        if (attr != null) {
484            ts.setTrackSpeed(Float.parseFloat(attr.getValue()));
485        }
486        
487        attr = element.getAttribute("beanName");
488        if (attr != null) {
489            String errMsg = ts.setNamedBean(cmd, attr.getValue());
490            if (errMsg != null) {
491                log.error("{} for {} in warrant {}", errMsg, ts.toString(), w.getDisplayName());
492            }
493        }
494        return ts;
495    }
496    
497    // pre 4.21.3
498//    @SuppressFBWarnings(value="NP_LOAD_OF_KNOWN_NULL_VALUE", justification="nothing wrong about a null return")
499    private static ThrottleSetting loadThrottleCommand(Element elem, Warrant w) {
500        long time = 0;
501        try {
502            time = elem.getAttribute("time").getLongValue();
503        } catch (org.jdom2.DataConversionException dce) {
504            log.warn("error loading throttle command");
505        }
506
507        Attribute attr = elem.getAttribute("command");
508        String command = null;
509        if (attr != null) {
510            command = attr.getValue();
511        } else {
512            log.error("Command type is null. ThrottleSetting not loaded for warrant {}", w.getDisplayName());
513            return null;
514        }
515
516        attr = elem.getAttribute("value");
517        String value = null;
518        if (attr != null)
519            value =attr.getValue();
520
521        attr = elem.getAttribute("block");
522        String block = null;
523        if (attr != null)
524            block =attr.getValue();
525
526        float speed = 0.0f;
527        attr = elem.getAttribute("trackSpeed");
528        if (attr != null) {
529            try {
530                speed = attr.getFloatValue();
531            } catch (DataConversionException ex) {
532                speed = 0.0f;
533                log.error("Unable to read speed of command.", ex);
534            }
535        }
536        
537        return new ThrottleSetting(time, command, value, block, speed);
538    }
539    
540    @Override
541    public int loadOrder(){
542        return InstanceManager.getDefault(WarrantManager.class).getXMLOrder();
543    }
544    
545    private final static Logger log = LoggerFactory.getLogger(WarrantManagerXml.class);
546
547}