001package jmri.jmrit.symbolicprog.tabbedframe; 002 003import java.awt.event.ActionEvent; 004import java.io.File; 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.List; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010 011import javax.swing.AbstractAction; 012import javax.swing.JFileChooser; 013import javax.swing.JPanel; 014 015import jmri.InstanceManager; 016import jmri.jmrit.XmlFile; 017import jmri.jmrit.symbolicprog.NameFile; 018import jmri.util.swing.JmriJOptionPane; 019 020import org.jdom2.Attribute; 021import org.jdom2.Element; 022import org.jdom2.JDOMException; 023 024/** 025 * Check the names in an XML programmer file against the names.xml definitions 026 * 027 * @author Bob Jacobsen Copyright (C) 2001, 2007 028 * @see jmri.jmrit.XmlFile 029 */ 030public class ProgCheckAction extends AbstractAction { 031 032 public ProgCheckAction(String s, JPanel who) { 033 super(s); 034 _who = who; 035 } 036 037 JFileChooser fci; 038 039 JPanel _who; 040 041 @Override 042 public void actionPerformed(ActionEvent e) { 043 if (fci == null) { 044 fci = jmri.jmrit.XmlFile.userFileChooser("XML files", "xml"); 045 } 046 // request the filename from an open dialog 047 fci.rescanCurrentDirectory(); 048 int retVal = fci.showOpenDialog(_who); 049 050 // handle selection or cancel 051 if (retVal == JFileChooser.APPROVE_OPTION) { 052 File file = fci.getSelectedFile(); 053 if (log.isDebugEnabled()) { 054 log.debug("located file {} for XML processing", file); 055 } 056 057 warnMissingNames(file); 058 059 // as ugly special case, do reverse check for Comprehensive programmer 060 if (file.getName().toLowerCase().endsWith("comprehensive.xml")) { 061 warnIncompleteComprehensive(file); 062 } 063 } else { 064 log.info("XmlFileCheckAction cancelled in open dialog"); 065 } 066 } 067 068 /** 069 * Find all of the display elements descending from this element. 070 * 071 * @param el the element to search 072 * @param list the list that will be populated with the found elements 073 */ 074 static protected void expandElement(Element el, List<Element> list) { 075 // get the leaves here 076 list.addAll(el.getChildren("display")); 077 078 List<Element> children = el.getChildren(); 079 for (int i = 0; i < children.size(); i++) { 080 expandElement(children.get(i), list); 081 } 082 } 083 084 /** 085 * Check for names in programer that are not in names.xml 086 * @param file A decoder definition XML file to be checked 087 */ 088 void warnMissingNames(File file) { 089 String result = checkMissingNames(file); 090 if (result.equals("")) { 091 JmriJOptionPane.showMessageDialog(_who, "OK, all variables in file are known"); 092 } else { 093 JmriJOptionPane.showMessageDialog(_who, result); 094 } 095 } 096 097 static String checkMissingNames(File file) { 098 try { 099 Element root = readFile(file); 100 log.debug("parsing complete"); 101 102 // check to see if there's a programmer element 103 if (root.getChild("programmer") == null) { 104 log.warn("Does not appear to be a programmer file"); 105 return "Does not appear to be a programmer file"; 106 } 107 108 // walk the entire tree of elements, saving a reference 109 // to all of the "display" elements 110 List<Element> varList = new ArrayList<>(); 111 expandElement(root.getChild("programmer"), varList); 112 log.debug("found {} display elements", varList.size()); 113 NameFile nfile = InstanceManager.getDefault(NameFile.class); 114 115 StringBuilder warnings = new StringBuilder(""); 116 117 for (int i = 0; i < varList.size(); i++) { 118 Element varElement = (varList.get(i)); 119 // for each variable, see if can find in names file 120 Attribute nameAttr = varElement.getAttribute("item"); 121 String name = null; 122 if (nameAttr != null) { 123 name = nameAttr.getValue(); 124 } 125 if (log.isDebugEnabled()) { 126 log.debug("Variable called \"{}\"", (name != null) ? name : "<none>"); 127 } 128 if (!(name == null ? false : nfile.checkName(name))) { 129 log.warn("Variable not found in name list: name=\"{}\"", name); 130 warnings.append("Variable not found in name list: name=\"").append(name).append("\"\n"); 131 } 132 } 133 134 String result = warnings.toString(); 135 return !result.isEmpty() ? "Names missing from Comprehensive.xml\n" + result : ""; 136 137 } catch (IOException | JDOMException ex) { 138 return "Error parsing programmer file: " + ex; 139 } 140 } 141 142 /** 143 * Check for names in names.xml that are not in file 144 * @param file A decoder definition XML file to be checked 145 */ 146 void warnIncompleteComprehensive(File file) { 147 String result = checkIncompleteComprehensive(file); 148 if (result.equals("")) { 149 JmriJOptionPane.showMessageDialog(_who, "OK, Comprehensive.xml is complete"); 150 } else { 151 JmriJOptionPane.showMessageDialog(_who, result); 152 } 153 } 154 155 static String checkIncompleteComprehensive(File file) { 156 // handle the file (later should be outside this thread?) 157 try { 158 Element root = readFile(file); 159 if (log.isDebugEnabled()) { 160 log.debug("parsing complete"); 161 } 162 163 // check to see if there's a programmer element 164 if (root.getChild("programmer") == null) { 165 log.warn("Does not appear to be a programmer file"); 166 return "Does not appear to be a programmer file"; 167 } 168 169 // walk the entire tree of elements, saving a reference 170 // to all of the "display" elements 171 List<Element> varList = new ArrayList<>(); 172 expandElement(root.getChild("programmer"), varList); 173 if (log.isDebugEnabled()) { 174 log.debug("found {} display elements", varList.size()); 175 } 176 NameFile nfile = InstanceManager.getDefault(NameFile.class); 177 178 StringBuilder warnings = new StringBuilder(""); 179 180 // for each item in names, see if found in this file 181 nfile.names().stream().filter((s) -> !(functionMapName(s))).forEachOrdered((s) -> { 182 boolean found = false; 183 for (int i = 0; i < varList.size(); i++) { 184 Element varElement = varList.get(i); 185 // for each variable, see if can find in names file 186 Attribute nameAttr = varElement.getAttribute("item"); 187 String name = null; 188 if (nameAttr != null) { 189 name = nameAttr.getValue(); 190 } 191 // now check 192 if (name != null && name.equals(s)) { 193 found = true; 194 } 195 } 196 if (!found) { 197 log.warn("Variable not in Comprehensive: name=\"{}\"", s); 198 warnings.append("Variable not in Comprehensive: name=\"").append(s).append("\"\n"); 199 } 200 }); 201 202 return warnings.toString(); 203 } catch (IOException | JDOMException ex) { 204 return "Error parsing programmer file: " + ex; 205 } 206 } 207 208 /** 209 * Check if the name is a function name, e.g. "F5 controls output 8" or 210 * "FL(f) controls output 14" 211 * @param name Possible function name to check 212 * @return true if the input is a valid name 213 */ 214 static boolean functionMapName(String name) { 215 if (numericPattern == null) { 216 numericPattern = Pattern.compile(numericRegex); 217 } 218 if (ffPattern == null) { 219 ffPattern = Pattern.compile(ffRegex); 220 } 221 if (frPattern == null) { 222 frPattern = Pattern.compile(frRegex); 223 } 224 225 Matcher matcher = numericPattern.matcher(name); 226 if (matcher.matches()) { 227 return true; 228 } 229 matcher = ffPattern.matcher(name); 230 if (matcher.matches()) { 231 return true; 232 } 233 matcher = frPattern.matcher(name); 234 return matcher.matches(); 235 } 236 static final String numericRegex = "^F(\\d++) controls output (\\d++)$"; 237 static volatile Pattern numericPattern; 238 static final String ffRegex = "^FL\\(f\\) controls output (\\d++)$"; 239 static volatile Pattern ffPattern; 240 static final String frRegex = "^FL\\(r\\) controls output (\\d++)$"; 241 static volatile Pattern frPattern; 242 243 /** 244 * Ask SAX to read and verify a file 245 * @param file XML-formatted input file 246 * @return root element if successful 247 * @throws org.jdom2.JDOMException if file can't be parsed 248 * @throws java.io.IOException if problems reading file 249 */ 250 static Element readFile(File file) throws org.jdom2.JDOMException, java.io.IOException { 251 XmlFile xf = new XmlFile() { 252 }; // odd syntax is due to XmlFile being abstract 253 254 return xf.rootFromFile(file); 255 256 } 257 258 // initialize logging 259 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ProgCheckAction.class); 260 261}