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