001package jmri.jmrix.cmri.serial.networkdriver.configurexml;
002
003import java.io.*;
004import java.nio.charset.StandardCharsets;
005import java.util.List;
006import java.util.StringTokenizer;
007
008import jmri.jmrit.XmlFile;
009import jmri.jmrix.cmri.CMRISystemConnectionMemo;
010import jmri.jmrix.cmri.serial.SerialNode;
011import jmri.jmrix.cmri.serial.SerialTrafficController;
012import jmri.jmrix.cmri.serial.networkdriver.ConnectionConfig;
013import jmri.jmrix.cmri.serial.networkdriver.NetworkDriverAdapter;
014import jmri.jmrix.configurexml.AbstractNetworkConnectionConfigXml;
015import jmri.util.FileUtil;
016import jmri.util.jdom.JDOMUtil;
017import jmri.util.prefs.JmriConfigurationProvider;
018import jmri.util.xml.XMLUtil;
019
020import org.jdom2.Element;
021import org.jdom2.JDOMException;
022import org.w3c.dom.Document;
023
024/**
025 * Handle XML persistence of layout connections by persisting the
026 * NetworkDriverAdapter (and connections).
027 * <p>
028 * Note this is named as the XML version of a ConnectionConfig object, but it's
029 * actually persisting the NetworkDriverAdapter.
030 * <p>
031 * This class is invoked from jmrix.JmrixConfigPaneXml on write, as that class
032 * is the one actually registered. Reads are brought here directly via the class
033 * attribute in the XML.
034 *
035 * @author Bob Jacobsen Copyright: Copyright (c) 2003, 2015
036 * @author kcameron Copyright (C) 2010 added multiple connections
037 */
038public class ConnectionConfigXml extends AbstractNetworkConnectionConfigXml {
039
040    public ConnectionConfigXml() {
041        super();
042    }
043
044    @Override
045    protected void getInstance() {
046        if(adapter == null) {
047           adapter = new NetworkDriverAdapter();
048        }
049    }
050
051    @Override
052    protected void getInstance(Object object) {
053        adapter = ((ConnectionConfig) object).getAdapter();
054    }
055
056    /**
057     * Write out the SerialNode objects too
058     *
059     * @param e Element being extended
060     */
061    @Override
062    protected void extendElement(Element e) {
063        var memo = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo());
064        String externalConfig = memo.getExternalConfig();
065        if (externalConfig != null) {
066            Element element = new Element("external-config");
067            element.addContent(externalConfig);
068            e.addContent(element);
069            storeExternalConfig(externalConfig);
070        } else {
071            storeConfig(e);
072        }
073    }
074
075    private void storeExternalConfig(String externalConfig) {
076        try {
077            Document doc = XMLUtil.createDocument("external-cmri-configuration", JmriConfigurationProvider.NAMESPACE, null, null); // NOI18N
078            var root = doc.getDocumentElement();
079            Element config = new Element("config");
080            storeConfig(config);
081            var w3cElement = JDOMUtil.toW3CElement(config);
082            root.appendChild(root.getOwnerDocument().importNode(w3cElement, true));
083            String filename = FileUtil.getExternalFilename(externalConfig);
084            try (final OutputStream os = new FileOutputStream(filename)) {
085                XMLUtil.write(doc, os, StandardCharsets.UTF_8.name());
086            }
087        } catch (IOException | JDOMException e) {
088            throw new RuntimeException(e);
089        }
090    }
091
092    private void storeConfig(Element e) {
093        // Create a polling list from the configured nodes
094        StringBuilder polllist = new StringBuilder("");
095        SerialTrafficController tcPL = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
096        SerialNode plNode = (SerialNode) tcPL.getNode(0);
097        int index = 1;
098        while (plNode != null) {
099            if (index != 1) {
100                polllist.append(",");
101            }
102            polllist.append(Integer.toString(plNode.getNodeAddress()));
103            plNode = (SerialNode) tcPL.getNode(index);
104            index++;
105        }
106
107        Element l = new Element("polllist");
108        l.setAttribute("pollseq", polllist.toString());
109        e.addContent(l);
110
111        SerialTrafficController tc = ((CMRISystemConnectionMemo)adapter.getSystemConnectionMemo()).getTrafficController();
112        SerialNode node = (SerialNode) tc.getNode(0);
113        index = 1;
114
115        while (node != null) {
116            // add node as an element
117            Element n = new Element("node");
118            n.setAttribute("name", "" + node.getNodeAddress());
119            e.addContent(n);
120            // add parameters to the node as needed
121            n.addContent(makeParameter("nodetype", "" + node.getNodeType()));
122            n.addContent(makeParameter("bitspercard", "" + node.getNumBitsPerCard()));
123            n.addContent(makeParameter("transmissiondelay", "" + node.getTransmissionDelay()));
124            n.addContent(makeParameter("num2lsearchlights", "" + node.getNum2LSearchLights()));
125            n.addContent(makeParameter("pulsewidth", "" + node.getPulseWidth()));
126            StringBuilder value = new StringBuilder("");
127            for (int i = 0; i < node.getLocSearchLightBits().length; i++) {
128                value.append(Integer.toHexString(node.getLocSearchLightBits()[i] & 0xF));
129            }
130            n.addContent(makeParameter("locsearchlightbits", value.toString()));
131            value = new StringBuilder("");
132            for (int i = 0; i < node.getCardTypeLocation().length; i++) {
133                value.append(Integer.toHexString(node.getCardTypeLocation()[i] & 0xF));
134            }
135            n.addContent(makeParameter("cardtypelocation", value.toString()));
136            log.debug("Node {} Card Type Written = {}", node.nodeAddress, value);
137
138            // CMRInet Options
139            value = new StringBuilder("");
140            for (int i = 0; i < SerialNode.NUMCMRINETOPTS; i++) {
141                value.append(Integer.toHexString((node.getCMRInetOpts(i) & 0xF)));
142            }
143            n.addContent(makeParameter("cmrinetoptions", value.toString().toUpperCase()));
144            log.debug("Node {} NET Options Written = {}", node.nodeAddress, value);
145
146            // cpNode Options  Classic CMRI nodes do not have options
147            if (node.getNodeType() == SerialNode.CPNODE || node.getNodeType() == SerialNode.CPMEGA) {
148                value = new StringBuilder();
149                for (int i = 0; i < SerialNode.NUMCPNODEOPTS; i++) {
150                    value.append(Integer.toHexString((node.getcpnodeOpts(i) & 0xF)));
151                }
152                n.addContent(makeParameter("cpnodeoptions", value.toString().toUpperCase()));
153                log.debug("Node {} NODE Options Written = {}", node.nodeAddress, value);
154            }
155
156            // node description
157            n.addContent(makeParameter("cmrinodedesc", node.getcmriNodeDesc()));
158
159            // look for the next node
160            node = (SerialNode) tc.getNode(index);
161            index++;
162        }
163    }
164
165    protected Element makeParameter(String name, String value) {
166        Element p = new Element("parameter");
167        p.setAttribute("name", name);
168        p.addContent(value);
169        return p;
170    }
171
172    @Override
173    protected void unpackElement(Element shared, Element perNode) {
174
175        Element externalConfig = shared.getChild("external-config");
176        if (externalConfig != null) {
177            loadExternalConfig(externalConfig);
178        } else {
179            loadConfig(shared);
180        }
181    }
182
183    private void loadExternalConfig(Element externalConfig) {
184        try {
185            String filename = externalConfig.getTextTrim();
186            ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo())
187                    .setExternalConfig(filename);
188            File file = new File(FileUtil.getExternalFilename(filename));
189            Element root = new XmlFile().rootFromURL(FileUtil.fileToURL(file));
190            Element config = root.getChild("config");
191            loadConfig(config);
192        } catch (IOException | JDOMException e) {
193            throw new RuntimeException(e);
194        }
195    }
196
197    private void loadConfig(Element shared) {
198        // --------------------------------------
199        // Load the poll list sequence if present
200        // --------------------------------------
201        List<Element> pl = shared.getChildren("polllist");
202        if (!pl.isEmpty()) {
203            Element ps = pl.get(0);
204            if (ps != null) {
205                String pseq = ps.getAttributeValue("pollseq");
206                if (pseq != null) {
207                    StringTokenizer nodes = new StringTokenizer(pseq, " ,");
208                    SerialTrafficController tcPL = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
209                    while (nodes.hasMoreTokens()) {
210                        tcPL.cmriNetPollList.add(Integer.parseInt(nodes.nextToken()));
211                    }
212                    log.debug("Poll List = {}", tcPL.cmriNetPollList);
213                }
214            }
215        }
216
217        // Load the node specific parameters
218        int pollListSize = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().cmriNetPollList.size();
219        int nextPollPos = pollListSize + 1;
220
221        List<Element> l = shared.getChildren("node");
222        for (int i = 0; i < l.size(); i++) {
223            Element n = l.get(i);
224            int addr = Integer.parseInt(n.getAttributeValue("name"));
225            int type = Integer.parseInt(findParmValue(n, "nodetype"));
226            int bpc = Integer.parseInt(findParmValue(n, "bitspercard"));
227            int delay = Integer.parseInt(findParmValue(n, "transmissiondelay"));
228            int num2l = Integer.parseInt(findParmValue(n, "num2lsearchlights"));
229            int pulseWidth = 500;
230            if ((findParmValue(n, "pulsewidth")) != null) {
231                pulseWidth = Integer.parseInt(findParmValue(n, "pulsewidth"));
232            }
233
234            String slb = findParmValue(n, "locsearchlightbits");
235            String ctl = findParmValue(n, "cardtypelocation");
236            String opts = "";
237
238            // create node (they register themselves)
239            SerialNode node = new SerialNode(addr, type,((CMRISystemConnectionMemo)adapter.getSystemConnectionMemo()).getTrafficController());
240            node.setNumBitsPerCard(bpc);
241            node.setTransmissionDelay(delay);
242            node.setNum2LSearchLights(num2l);
243            node.setPulseWidth(pulseWidth);
244
245            // From the loaded poll list, assign the poll list position to the node
246            boolean assigned = false;
247            if (pollListSize > 0) {
248                for (int pls = 0; pls < pollListSize; pls++) {
249                    if (((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().cmriNetPollList.get(pls) == node.getNodeAddress()) {
250                        node.setPollListPosition(pls + 1);
251                        assigned = true;
252                    }
253                }
254                if (!assigned) {
255                    node.setPollListPosition(nextPollPos++);
256                }
257            }
258
259            // CMRInet Options
260            //----------------
261            if (findParmValue(n, "cmrinetoptions") != null) {
262                opts = findParmValue(n, "cmrinetoptions");
263                // Convert and load the  value into the node options array
264                for (int j = 0; j < SerialNode.NUMCMRINETOPTS; j++) {
265                    node.setCMRInetOpts(j, (opts.charAt(j) - '0'));
266                }
267                log.debug("Node {} NET Options Read = {}", node.nodeAddress, opts);
268
269            } else {
270                // This must be the first time the nodes were loaded into a cpNode
271                // supported version.  Set the Auto Poll option to avoid confusion
272                // with installed configurations.
273                for (int j = 0; j < SerialNode.NUMCMRINETOPTS; j++) {
274                    node.setCMRInetOpts(j, 0);
275                }
276                node.setOptNet_AUTOPOLL(1);
277                log.debug("Node {} AUTO POLL Set ", node.nodeAddress);
278
279            }
280
281            for (int j = 0; j < slb.length(); j++) {
282                node.setLocSearchLightBits(j, (slb.charAt(j) - '0'));
283            }
284
285            for (int j = 0; j < ctl.length(); j++) {
286                node.setCardTypeLocation(j, (ctl.charAt(j) - '0'));
287            }
288
289            if (type == SerialNode.CPNODE || type == SerialNode.CPMEGA) {
290                // cpNode Options
291                if (findParmValue(n, "cpnodeoptions") != null) {
292                    opts = findParmValue(n, "cpnodeoptions");
293                    // Convert and load the  value into the node options array
294                    for (int j = 0; j < SerialNode.NUMCPNODEOPTS; j++) {
295                        node.setcpnodeOpts(j, (opts.charAt(j) - '0'));
296                    }
297                }
298
299                log.debug("Node {} NODE Options Read = {}", node.nodeAddress, opts);
300            }
301
302            if (findParmValue(n, "cmrinodedesc") != null) {
303                node.setcmriNodeDesc(findParmValue(n, "cmrinodedesc"));
304            } else {
305                log.debug("No Description - Node {}", addr);
306            }
307
308            // Trigger initialization of this Node to reflect these parameters
309            ((CMRISystemConnectionMemo)adapter.getSystemConnectionMemo()).getTrafficController().initializeSerialNode(node);
310        }
311    }
312
313    @Override
314    protected void register() {
315        this.register(new ConnectionConfig(adapter));
316    }
317
318    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConnectionConfigXml.class);
319}