001package jmri.jmrit.display.layoutEditor;
002
003import static jmri.jmrit.XmlFile.newDocument;
004import static jmri.jmrit.XmlFile.xsltLocation;
005
006import java.io.File;
007import java.io.IOException;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011import java.util.ArrayList;
012import jmri.BasicRosterEntry;
013import jmri.Block;
014import jmri.BlockManager;
015import jmri.Path;
016import jmri.jmrit.XmlFile;
017import jmri.jmrit.roster.Roster;
018import jmri.jmrit.roster.RosterEntry;
019import jmri.util.FileUtil;
020import jmri.PowerManager;
021import jmri.JmriException;
022import org.jdom2.Attribute;
023import org.jdom2.DataConversionException;
024import org.jdom2.Document;
025import org.jdom2.Element;
026import org.jdom2.JDOMException;
027import org.jdom2.ProcessingInstruction;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Handle saving/restoring block value information to XML files. This class
033 * manipulates files conforming to the block_value DTD.
034 *
035 * @author Dave Duchamp Copyright (C) 2008
036 * @author George Warner Copyright (c) 2017-2018
037 */
038public class BlockValueFile extends XmlFile {
039
040    public BlockValueFile() {
041        super();
042        blockManager = jmri.InstanceManager.getDefault(jmri.BlockManager.class);
043    }
044
045    // operational variables
046    private BlockManager blockManager = null;
047    private final static String defaultFileName = FileUtil.getUserFilesPath() + "blockvalues.xml";
048    private Element root = null;
049
050    /**
051     * Reads Block values from a file in the user's preferences directory. If
052     * the file containing block values does not exist this routine returns
053     * quietly. If a Block named in the file does not exist currently, that
054     * entry is quietly ignored.
055     *
056     * @throws JDOMException on rootFromName if all methods fail
057     * @throws IOException   if an I/O error occurs while reading a file
058     */
059    public void readBlockValues() throws JDOMException, IOException {
060        log.debug("entered readBlockValues");
061        List<String> blocks = new ArrayList<>(blockManager.getNamedBeanSet().size());
062        blockManager.getNamedBeanSet().forEach(bean -> {
063            blocks.add(bean.getSystemName());
064        });
065        // check if file exists
066        if (checkFile(defaultFileName)) {
067            // file is present,
068            root = rootFromName(defaultFileName);
069            if ((root != null) && (blocks.size() > 0)) {
070                // there is a file and there are Blocks defined
071                Element blockvalues = root.getChild("blockvalues");
072                if (blockvalues != null) {
073                    // there are values defined, read and set block values if Block exists.
074                    List<Element> blockList = blockvalues.getChildren("block");
075                    // check if all powermanagers are turned on, if they are, we should expect
076                    // blocks with values to be occupied
077                    boolean allPoweredUp = true;
078                    for (PowerManager pm : jmri.InstanceManager.getList(PowerManager.class)) {
079                        if (pm.getPower() != jmri.PowerManager.ON) {
080                            allPoweredUp = false;
081                        }
082                    }
083                    List<String> passes = new ArrayList<>();
084                    passes.add("set");
085                    if (allPoweredUp) {
086                        // perform two passes, one to check blocks with values are occupied, the second to
087                        // set values
088                        passes.add(0, "check");
089                    }
090                    for (String pass : passes) {
091                        for (Element bl : blockList) {
092                            if (bl.getAttribute("systemname") == null) {
093                                log.warn("unexpected null in systemName {} {}", bl, bl.getAttributes());
094                                break;
095                            }
096                            String sysName = bl.getAttribute("systemname").getValue();
097                            // get Block - ignore entry if block not found
098                            Block b = blockManager.getBySystemName(sysName);
099                            if (b != null) {
100                                // Block was found
101                                if (pass.equals("check") && b.getState() != Block.OCCUPIED) {
102                                    // we have a recorded value for an empty block, the blockvalues file
103                                    // must be out of date, bail out before we set any values
104                                    log.error("block {} is not occupied but has a saved value, not setting saved block values",
105                                            b.getDisplayName());
106                                    return;
107                                }
108                                if (pass.equals("set")) {
109                                    Object v = bl.getAttribute("value").getValue();
110                                    if (bl.getAttribute("valueClass") != null) {
111                                        if (bl.getAttribute("valueClass").getValue().equals("jmri.jmrit.roster.RosterEntry")) {
112                                            RosterEntry re = Roster.getDefault().getEntryForId(((String) v));
113                                            if (re != null) {
114                                                v = re;
115                                            }
116                                        }
117                                    }
118                                    b.setValue(v);
119                                }
120                                if (pass.equals("set")) {
121                                    // set direction if there is one
122                                    int dd = Path.NONE;
123                                    Attribute a = bl.getAttribute("dir");
124                                    if (a != null) {
125                                        try {
126                                            dd = a.getIntValue();
127                                        } catch (DataConversionException e) {
128                                            log.error("failed to convert direction attribute");
129                                        }
130                                    }
131                                    b.setDirection(dd);
132                                }
133                            }
134                        }
135                    }
136                }
137            }
138        }
139    }
140
141
142    /*
143     *  Writes out block values to a file in the user's preferences directory
144     *  If there are no defined Blocks, no file is written.
145     *  If none of the defined Blocks have values, no file is written.
146     *
147     * @throws IOException
148     */
149    public void writeBlockValues() throws IOException {
150        log.debug("entered writeBlockValues");
151        if (blockManager.getNamedBeanSet().size() > 0) {
152            // there are blocks defined, create root element
153            root = new Element("block_values");
154            Document doc = newDocument(root, dtdLocation + "block-values.dtd");
155            boolean valuesFound = false;
156
157            // add XSLT processing instruction
158            // <?xml-stylesheet type="text/xsl" href="XSLT/block-values.xsl"?>
159            Map<String, String> m = new HashMap<>();
160            m.put("type", "text/xsl");
161            m.put("href", xsltLocation + "blockValues.xsl");
162            ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
163            doc.addContent(0, p);
164
165            // save block values in xml format
166            Element values = new Element("blockvalues");
167            
168            for (Block b : blockManager.getNamedBeanSet()) {
169                if (b != null) {
170                    Object o = b.getValue();
171                    if (o != null) {
172                        // block has value, save it
173                        Element val = new Element("block");
174                        val.setAttribute("systemname", b.getSystemName());
175                        if (o instanceof RosterEntry) {
176                            val.setAttribute("value", ((BasicRosterEntry) o).getId());
177                            val.setAttribute("valueClass", "jmri.jmrit.roster.RosterEntry");
178                        } else {
179                            val.setAttribute("value", o.toString());
180                        }
181                        int v = b.getDirection();
182                        if (v != Path.NONE) {
183                            val.setAttribute("dir", "" + v);
184                        }
185                        values.addContent(val);
186                        valuesFound = true;
187                    }
188                } else {
189                    log.error("Block null in blockManager.getNamedBeanSet()");
190                }
191            }
192            root.addContent(values);
193
194            // write out the file if values were found
195            if (valuesFound) {
196                try {
197                    if (!checkFile(defaultFileName)) {
198                        // file does not exist, create it
199                        File file = new File(defaultFileName);
200                        if (!file.createNewFile()) // create and check result
201                        {
202                            log.error("createNewFile failed");
203                        }
204                    }
205                    // write content to file
206                    writeXML(findFile(defaultFileName), doc);
207                } catch (IOException ioe) {
208                    log.error("While writing block value file ", ioe);
209                    throw (ioe);
210                }
211            }
212        }
213    }
214
215    // initialize logging
216    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockValueFile.class);
217
218}