001package jmri.jmrit.consisttool; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.io.File; 006import java.io.IOException; 007import java.util.*; 008 009import jmri.Consist; 010import jmri.ConsistManager; 011import jmri.LocoAddress; 012import jmri.DccLocoAddress; 013import jmri.InstanceManager; 014import jmri.jmrit.XmlFile; 015import jmri.jmrit.roster.Roster; 016import jmri.jmrit.roster.RosterConfigManager; 017import jmri.util.FileUtil; 018import org.jdom2.Attribute; 019import org.jdom2.Document; 020import org.jdom2.Element; 021import org.jdom2.JDOMException; 022import org.jdom2.ProcessingInstruction; 023import org.jdom2.filter.ElementFilter; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Handle saving/restoring consist information to XML files. This class 029 * manipulates files conforming to the consist-roster-config DTD. 030 * 031 * @author Paul Bender Copyright (C) 2008 032 */ 033public class ConsistFile extends XmlFile implements PropertyChangeListener { 034 035 private static final String CONSIST = "consist"; // NOI18N 036 private static final String CONSISTID = "id"; // NOI18N 037 private static final String CONSISTNUMBER = "consistNumber"; // NOI18N 038 private static final String DCCLOCOADDRESS = "dccLocoAddress"; // NOI18N 039 private static final String LONGADDRESS = "longAddress"; // NOI18N 040 private static final String LOCODIR = "locoDir"; // NOI18N 041 private static final String LOCONAME = "locoName"; // NOI18N 042 private static final String LOCOROSTERID = "locoRosterId"; // NOI18N 043 private static final String NORMAL = "normal"; // NOI18N 044 private static final String REVERSE = "reverse"; // NOI18N 045 046 protected ConsistManager consistMan = null; 047 048 public ConsistFile() { 049 super(); 050 consistMan = InstanceManager.getDefault(jmri.ConsistManager.class); 051 Roster.getDefault().addPropertyChangeListener(this); 052 } 053 054 /** 055 * Load a Consist from the consist elements in the file. 056 * 057 * @param consist a JDOM element containing a consist 058 */ 059 private void consistFromXml(Element consist) { 060 Attribute cnumber; 061 Attribute isCLong; 062 Consist newConsist; 063 064 // Read the consist address from the file and create the 065 // consisit in memory if it doesn't exist already. 066 cnumber = consist.getAttribute(CONSISTNUMBER); 067 isCLong = consist.getAttribute(LONGADDRESS); 068 DccLocoAddress consistAddress; 069 if (isCLong != null) { 070 log.debug("adding consist {} with longAddress set to {}.", cnumber, isCLong.getValue()); 071 try { 072 int number = Integer.parseInt(cnumber.getValue()); 073 consistAddress = new DccLocoAddress(number, isCLong.getValue().equals("yes")); 074 } catch (NumberFormatException e) { 075 log.debug("Consist number not an integer"); 076 return; 077 } 078 079 } else { 080 log.debug("adding consist {} with default long address setting.", cnumber); 081 consistAddress = new DccLocoAddress(Integer.parseInt(cnumber.getValue()), false); 082 } 083 newConsist = consistMan.getConsist(consistAddress); 084 if (!(newConsist.getConsistList().isEmpty())) { 085 log.debug("Consist {} is not empty. Using version in memory.", consistAddress); 086 return; 087 } 088 089 readConsistType(consist, newConsist); 090 readConsistId(consist, newConsist); 091 readConsistLocoList(consist,newConsist); 092 consistMan.notifyConsistListChanged(); 093 } 094 095 public void readConsistLocoList(Element consist, Consist newConsist) { 096 // read each child of locomotive in the consist from the file 097 // and restore it's information to memory. 098 Iterator<Element> childIterator = consist.getDescendants(new ElementFilter("loco")); 099 try { 100 Element e; 101 do { 102 e = childIterator.next(); 103 Attribute number = e.getAttribute(DCCLOCOADDRESS); 104 log.debug("adding Loco {}", number); 105 DccLocoAddress address = readLocoAddress(e); 106 107 Attribute direction = e.getAttribute(LOCODIR); 108 boolean directionNormal = false; 109 if (direction != null) { 110 // use the values from the file 111 log.debug("using direction from file {}", direction.getValue()); 112 directionNormal = direction.getValue().equals(NORMAL); 113 } else { 114 // use default, normal direction 115 directionNormal = true; 116 } 117 // Use restore so we DO NOT cause send any commands 118 // to the command station as we recreate the consist. 119 newConsist.restore(address,directionNormal); 120 readLocoPosition(e,address,newConsist); 121 Attribute rosterId = e.getAttribute(LOCOROSTERID); 122 if (rosterId != null) { 123 newConsist.setRosterId(address, rosterId.getValue()); 124 } 125 } while (true); 126 } catch (NoSuchElementException nse) { 127 log.debug("end of loco list"); 128 } 129 } 130 131 private void readConsistType(Element consist, Consist newConsist){ 132 // read and set the consist type 133 Attribute type = consist.getAttribute("type"); 134 if (type != null) { 135 // use the value read from the file 136 newConsist.setConsistType((type.getValue().equals("CSAC")) ? Consist.CS_CONSIST : Consist.ADVANCED_CONSIST); 137 } else { 138 // use the default (DAC) 139 newConsist.setConsistType(Consist.ADVANCED_CONSIST); 140 } 141 } 142 143 private void readConsistId(Element consist,Consist newConsist){ 144 // Read the consist ID from the file 145 Attribute cID = consist.getAttribute(CONSISTID); 146 if (cID != null) { 147 // use the value read from the file 148 newConsist.setConsistID(cID.getValue()); 149 } 150 } 151 152 private void readLocoPosition(Element loco,DccLocoAddress address, Consist newConsist){ 153 Attribute position = loco.getAttribute(LOCONAME); 154 if (position != null && !position.getValue().equals("mid")) { 155 if (position.getValue().equals("lead")) { 156 newConsist.setPosition(address, Consist.POSITION_LEAD); 157 } else if (position.getValue().equals("rear")) { 158 newConsist.setPosition(address, Consist.POSITION_TRAIL); 159 } 160 } else { 161 Attribute midNumber = loco.getAttribute("locoMidNumber"); 162 if (midNumber != null) { 163 int pos = Integer.parseInt(midNumber.getValue()); 164 newConsist.setPosition(address, pos); 165 } 166 } 167 } 168 169 private DccLocoAddress readLocoAddress(Element loco){ 170 DccLocoAddress address; 171 Attribute number = loco.getAttribute(DCCLOCOADDRESS); 172 Attribute isLong = loco.getAttribute(LONGADDRESS); 173 if (isLong != null ) { 174 // use the values from the file 175 address = new DccLocoAddress( 176 Integer.parseInt(number.getValue()), 177 isLong.getValue().equals("yes")); 178 } else { 179 // set as long address 180 address = new DccLocoAddress( 181 Integer.parseInt(number.getValue()), 182 true); 183 } 184 185 return address; 186 } 187 188 /** 189 * convert a Consist to XML. 190 * 191 * @param consist a Consist object to write to the file 192 * @return an Element representing the consist. 193 */ 194 private Element consistToXml(Consist consist) { 195 Element e = new Element(CONSIST); 196 e.setAttribute(CONSISTID, consist.getConsistID()); 197 e.setAttribute(CONSISTNUMBER, "" + consist.getConsistAddress() 198 .getNumber()); 199 e.setAttribute(LONGADDRESS, consist.getConsistAddress() 200 .isLongAddress() ? "yes" : "no"); 201 e.setAttribute("type", consist.getConsistType() == Consist.ADVANCED_CONSIST ? "DAC" : "CSAC"); 202 ArrayList<DccLocoAddress> addressList = consist.getConsistList(); 203 204 for (int i = 0; i < addressList.size(); i++) { 205 DccLocoAddress locoaddress = addressList.get(i); 206 Element eng = new Element("loco"); 207 eng.setAttribute(DCCLOCOADDRESS, "" + locoaddress.getNumber()); 208 eng.setAttribute(LONGADDRESS, locoaddress.isLongAddress() ? "yes" : "no"); 209 eng.setAttribute(LOCODIR, consist.getLocoDirection(locoaddress) ? NORMAL : REVERSE); 210 int position = consist.getPosition(locoaddress); 211 switch (position) { 212 case Consist.POSITION_LEAD: 213 eng.setAttribute(LOCONAME, "lead"); 214 break; 215 case Consist.POSITION_TRAIL: 216 eng.setAttribute(LOCONAME, "rear"); 217 break; 218 default: 219 eng.setAttribute(LOCONAME, "mid"); 220 eng.setAttribute("locoMidNumber", "" + position); 221 break; 222 } 223 String rosterId = consist.getRosterId(locoaddress); 224 if (rosterId != null) { 225 eng.setAttribute(LOCOROSTERID, rosterId); 226 } 227 e.addContent(eng); 228 } 229 return (e); 230 } 231 232 /** 233 * Read all consists from the default file name. 234 * 235 * @throws org.jdom2.JDOMException if unable to parse consists 236 * @throws java.io.IOException if unable to read file 237 */ 238 public void readFile() throws JDOMException, IOException { 239 readFile(defaultConsistFilename()); 240 } 241 242 /** 243 * Read all consists from a file. 244 * 245 * @param fileName path to file 246 * @throws org.jdom2.JDOMException if unable to parse consists 247 * @throws java.io.IOException if unable to read file 248 */ 249 public void readFile(String fileName) throws JDOMException, IOException { 250 if (checkFile(fileName)) { 251 Element root = rootFromName(fileName); 252 Element roster; 253 if (root == null) { 254 log.warn("consist file could not be read"); 255 return; 256 } 257 roster = root.getChild("roster"); 258 if (roster == null) { 259 log.debug("consist file does not contain a roster entry"); 260 return; 261 } 262 Iterator<Element> consistIterator = root.getDescendants(new ElementFilter(CONSIST)); 263 try { 264 Element consist; 265 do { 266 consist = consistIterator.next(); 267 consistFromXml(consist); 268 } while (consistIterator.hasNext()); 269 } catch (NoSuchElementException nde) { 270 log.debug("end of consist list"); 271 } 272 } else { 273 log.info("Consist file does not exist. One will be created if necessary."); 274 } 275 276 } 277 278 /** 279 * Write all consists to the default file name. 280 * 281 * @param consistList list of consist addresses 282 * @throws java.io.IOException if unable to write file 283 */ 284 public void writeFile(List<LocoAddress> consistList) throws IOException { 285 writeFile(consistList, defaultConsistFilename()); 286 } 287 288 /** 289 * Write all consists to a file. 290 * 291 * @param consistList list of consist addresses 292 * @param fileName path to file 293 * @throws java.io.IOException if unable to write file 294 */ 295 public void writeFile(List<LocoAddress> consistList, String fileName) throws IOException { 296 // create root element 297 Element root = new Element("consist-roster-config"); 298 Document doc = newDocument(root, dtdLocation + "consist-roster-config.dtd"); 299 300 // add XSLT processing instruction 301 Map<String, String> m = new HashMap<>(); 302 m.put("type", "text/xsl"); 303 m.put("href", xsltLocation + "consistRoster.xsl"); 304 ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m); 305 doc.addContent(0, p); 306 307 Element roster = new Element("roster"); 308 309 for (int i = 0; i < consistList.size(); i++) { 310 Consist newConsist = consistMan.getConsist(consistList.get(i)); 311 roster.addContent(consistToXml(newConsist)); 312 } 313 root.addContent(roster); 314 if (!checkFile(fileName)) { 315 //The file does not exist, create it before writing 316 File file = new File(fileName); 317 // verify the directory exists. 318 File parentDir = file.getParentFile(); 319 FileUtil.createDirectory(parentDir); 320 if (!file.createNewFile()) { 321 throw (new IOException()); 322 } 323 } 324 writeXML(findFile(fileName), doc); 325 } 326 327 /** 328 * GetFile Location. 329 * 330 * @return the preferences subdirectory in which Consist Files are kept 331 * this is relative to the roster files location. 332 */ 333 public static String getFileLocation() { 334 return Roster.getDefault().getRosterFilesLocation() + CONSIST + File.separator; 335 } 336 337 /** 338 * Get the filename for the default Consist file, including location. 339 * 340 * @return the filename 341 */ 342 public static String defaultConsistFilename() { 343 return getFileLocation() + "consist.xml"; 344 } 345 346 /** 347 * {@inheritDoc} 348 */ 349 @Override 350 public void propertyChange(PropertyChangeEvent evt) { 351 if (evt.getSource() instanceof Roster && 352 evt.getPropertyName().equals(RosterConfigManager.DIRECTORY)) { 353 try { 354 this.writeFile(consistMan.getConsistList()); 355 } catch (IOException ioe) { 356 log.error("Unable to write consist information to new consist folder"); 357 } 358 } 359 } 360 361 // initialize logging 362 private static final Logger log = LoggerFactory.getLogger(ConsistFile.class); 363}