001package jmri.jmrix.nce.consist; 002 003import java.io.IOException; 004import java.io.Writer; 005import java.util.List; 006import java.util.StringTokenizer; 007import java.util.Vector; 008import jmri.InstanceManager; 009import jmri.util.davidflanagan.HardcopyWriter; 010import org.jdom2.Element; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * ConsistRosterEntry represents a single element in a consist roster. 016 * <p> 017 * The ConsistRosterEntry is the central place to find information about a 018 * consists configuration, including loco address, address type, loco's 019 * direction, and consist number. Up to six consist locos are currently tracked. 020 * ConsistRosterEntry handles persistency through the LocoFile class. Creating a 021 * ConsistRosterEntry does not necessarily read the corresponding file (which 022 * might not even exist), please see readFile(), writeFile() member functions. 023 * <p> 024 * All the data attributes have a content, not null. 025 * <p> 026 * When the filePath attribute is non-null, the user has decided to organize the 027 * roster into directories. 028 * 029 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2004, 2005 030 * @author Dennis Miller Copyright 2004 031 * @author Daniel Boudreau (C) 2008 032 * @see NceConsistRoster 033 * 034 */ 035public class NceConsistRosterEntry { 036 037 /** 038 * Construct a blank object. 039 * 040 */ 041 public NceConsistRosterEntry() { 042 } 043 044 public NceConsistRosterEntry(NceConsistRosterEntry pEntry, String pID) { 045 this(); 046 // The ID is different for this element 047 _id = pID; 048 049 // All other items are copied 050 _roadName = pEntry._roadName; 051 _roadNumber = pEntry._roadNumber; 052 _model = pEntry._model; 053 _consistNumber = pEntry._consistNumber; 054 _loco1DccAddress = pEntry._loco1DccAddress; 055 _isLoco1LongAddress = pEntry._isLoco1LongAddress; 056 _loco2DccAddress = pEntry._loco2DccAddress; 057 _isLoco2LongAddress = pEntry._isLoco2LongAddress; 058 _loco3DccAddress = pEntry._loco3DccAddress; 059 _isLoco3LongAddress = pEntry._isLoco3LongAddress; 060 _loco4DccAddress = pEntry._loco4DccAddress; 061 _isLoco4LongAddress = pEntry._isLoco4LongAddress; 062 _loco5DccAddress = pEntry._loco5DccAddress; 063 _isLoco5LongAddress = pEntry._isLoco5LongAddress; 064 _loco6DccAddress = pEntry._loco6DccAddress; 065 _isLoco6LongAddress = pEntry._isLoco6LongAddress; 066 067 _comment = pEntry._comment; 068 } 069 070 public void setId(String s) { 071 String oldID = _id; 072 _id = s; 073 if (!oldID.equals(s)) { 074 InstanceManager.getDefault(NceConsistRoster.class).entryIdChanged(this); 075 } 076 } 077 078 public String getId() { 079 return _id; 080 } 081 082 public void setConsistNumber(String s) { 083 _consistNumber = s; 084 } 085 086 public String getConsistNumber() { 087 return _consistNumber; 088 } 089 090 public void setRoadName(String s) { 091 _roadName = s; 092 } 093 094 public String getRoadName() { 095 return _roadName; 096 } 097 098 public void setRoadNumber(String s) { 099 _roadNumber = s; 100 } 101 102 public String getRoadNumber() { 103 return _roadNumber; 104 } 105 106 public void setModel(String s) { 107 _model = s; 108 } 109 110 public String getModel() { 111 return _model; 112 } 113 114 public void setLoco1DccAddress(String s) { 115 _loco1DccAddress = s; 116 } 117 118 public String getLoco1DccAddress() { 119 return _loco1DccAddress; 120 } 121 122 public void setLoco1LongAddress(boolean b) { 123 _isLoco1LongAddress = b; 124 } 125 126 public boolean isLoco1LongAddress() { 127 return _isLoco1LongAddress; 128 } 129 130 public void setLoco1Direction(String s) { 131 _loco1Direction = s; 132 } 133 134 public String getLoco1Direction() { 135 return _loco1Direction; 136 } 137 138 public void setLoco2DccAddress(String s) { 139 _loco2DccAddress = s; 140 } 141 142 public String getLoco2DccAddress() { 143 return _loco2DccAddress; 144 } 145 146 public void setLoco2LongAddress(boolean b) { 147 _isLoco2LongAddress = b; 148 } 149 150 public boolean isLoco2LongAddress() { 151 return _isLoco2LongAddress; 152 } 153 154 public void setLoco2Direction(String s) { 155 _loco2Direction = s; 156 } 157 158 public String getLoco2Direction() { 159 return _loco2Direction; 160 } 161 162 public void setLoco3DccAddress(String s) { 163 _loco3DccAddress = s; 164 } 165 166 public String getLoco3DccAddress() { 167 return _loco3DccAddress; 168 } 169 170 public void setLoco3LongAddress(boolean b) { 171 _isLoco3LongAddress = b; 172 } 173 174 public boolean isLoco3LongAddress() { 175 return _isLoco3LongAddress; 176 } 177 178 public void setLoco3Direction(String s) { 179 _loco3Direction = s; 180 } 181 182 public String getLoco3Direction() { 183 return _loco3Direction; 184 } 185 186 public void setLoco4DccAddress(String s) { 187 _loco4DccAddress = s; 188 } 189 190 public String getLoco4DccAddress() { 191 return _loco4DccAddress; 192 } 193 194 public void setLoco4LongAddress(boolean b) { 195 _isLoco4LongAddress = b; 196 } 197 198 public boolean isLoco4LongAddress() { 199 return _isLoco4LongAddress; 200 } 201 202 public void setLoco4Direction(String s) { 203 _loco4Direction = s; 204 } 205 206 public String getLoco4Direction() { 207 return _loco4Direction; 208 } 209 210 public void setLoco5DccAddress(String s) { 211 _loco5DccAddress = s; 212 } 213 214 public String getLoco5DccAddress() { 215 return _loco5DccAddress; 216 } 217 218 public void setLoco5LongAddress(boolean b) { 219 _isLoco5LongAddress = b; 220 } 221 222 public boolean isLoco5LongAddress() { 223 return _isLoco5LongAddress; 224 } 225 226 public void setLoco5Direction(String s) { 227 _loco5Direction = s; 228 } 229 230 public String getLoco5Direction() { 231 return _loco5Direction; 232 } 233 234 public void setLoco6DccAddress(String s) { 235 _loco6DccAddress = s; 236 } 237 238 public String getLoco6DccAddress() { 239 return _loco6DccAddress; 240 } 241 242 public void setLoco6LongAddress(boolean b) { 243 _isLoco6LongAddress = b; 244 } 245 246 public boolean isLoco6LongAddress() { 247 return _isLoco6LongAddress; 248 } 249 250 public void setLoco6Direction(String s) { 251 _loco6Direction = s; 252 } 253 254 public String getLoco6Direction() { 255 return _loco6Direction; 256 } 257 258 public void setComment(String s) { 259 _comment = s; 260 } 261 262 public String getComment() { 263 return _comment; 264 } 265 266 /** 267 * Construct this Entry from XML. This member has to remain synchronized 268 * with the detailed DTD in xml/DTD/consist-roster-config.dtd. 269 * 270 * @param e Consist XML element 271 */ 272 public NceConsistRosterEntry(org.jdom2.Element e) { 273 if (log.isDebugEnabled()) { 274 log.debug("ctor from element {}", e); 275 } 276 org.jdom2.Attribute a; 277 if ((a = e.getAttribute("id")) != null) { 278 _id = a.getValue(); 279 } else { 280 log.warn("no id attribute in consist element when reading ConsistRoster"); // NOI18N 281 } 282 283 if ((a = e.getAttribute("consistNumber")) != null) { 284 _consistNumber = a.getValue(); 285 } 286 if ((a = e.getAttribute("roadName")) != null) { 287 _roadName = a.getValue(); 288 } 289 if ((a = e.getAttribute("roadNumber")) != null) { 290 _roadNumber = a.getValue(); 291 } 292 if ((a = e.getAttribute("model")) != null) { 293 _model = a.getValue(); 294 } 295 if ((a = e.getAttribute("comment")) != null) { 296 _comment = a.getValue(); 297 } 298 299 List<Element> elementList = e.getChildren("loco"); 300 301 for (Element element : elementList) { 302 String locoName = ""; 303 String locoMidNumber = ""; 304 if ((a = element.getAttribute("locoName")) != null) { 305 locoName = a.getValue(); 306 } 307 if ((a = element.getAttribute("locoMidNumber")) != null) { 308 locoMidNumber = a.getValue(); 309 } 310 311 if (locoName.equals("lead")) { 312 if ((a = element.getAttribute("dccLocoAddress")) != null) { 313 _loco1DccAddress = a.getValue(); 314 } 315 if ((a = element.getAttribute("longAddress")) != null) { 316 setLoco1LongAddress(a.getValue().equals("yes")); 317 } 318 if ((a = element.getAttribute("locoDir")) != null) { 319 _loco1Direction = (a.getValue()); 320 } 321 } 322 if (locoName.equals("rear")) { 323 if ((a = element.getAttribute("dccLocoAddress")) != null) { 324 _loco2DccAddress = a.getValue(); 325 } 326 if ((a = element.getAttribute("longAddress")) != null) { 327 setLoco2LongAddress(a.getValue().equals("yes")); 328 } 329 if ((a = element.getAttribute("locoDir")) != null) { 330 _loco2Direction = (a.getValue()); 331 } 332 } 333 if (locoName.equals("mid") && locoMidNumber.equals("1")) { 334 if ((a = element.getAttribute("dccLocoAddress")) != null) { 335 _loco3DccAddress = a.getValue(); 336 } 337 if ((a = element.getAttribute("longAddress")) != null) { 338 setLoco3LongAddress(a.getValue().equals("yes")); 339 } 340 if ((a = element.getAttribute("locoDir")) != null) { 341 _loco3Direction = (a.getValue()); 342 } 343 } 344 if (locoName.equals("mid") && locoMidNumber.equals("2")) { 345 if ((a = element.getAttribute("dccLocoAddress")) != null) { 346 _loco4DccAddress = a.getValue(); 347 } 348 if ((a = element.getAttribute("longAddress")) != null) { 349 setLoco4LongAddress(a.getValue().equals("yes")); 350 } 351 if ((a = element.getAttribute("locoDir")) != null) { 352 _loco4Direction = (a.getValue()); 353 } 354 } 355 if (locoName.equals("mid") && locoMidNumber.equals("3")) { 356 if ((a = element.getAttribute("dccLocoAddress")) != null) { 357 _loco5DccAddress = a.getValue(); 358 } 359 if ((a = element.getAttribute("longAddress")) != null) { 360 setLoco5LongAddress(a.getValue().equals("yes")); 361 } 362 if ((a = element.getAttribute("locoDir")) != null) { 363 _loco5Direction = (a.getValue()); 364 } 365 } 366 if (locoName.equals("mid") && locoMidNumber.equals("4")) { 367 if ((a = element.getAttribute("dccLocoAddress")) != null) { 368 _loco6DccAddress = a.getValue(); 369 } 370 if ((a = element.getAttribute("longAddress")) != null) { 371 setLoco6LongAddress(a.getValue().equals("yes")); 372 } 373 if ((a = element.getAttribute("locoDir")) != null) { 374 _loco6Direction = (a.getValue()); 375 } 376 } 377 } 378 if (_loco1DccAddress.equals("")) { 379 log.warn("no lead loco attribute in consist element when reading ConsistRoster"); // NOI18N 380 } 381 if (_loco2DccAddress.equals("")) { 382 log.warn("no rear loco attribute in consist element when reading ConsistRoster"); // NOI18N 383 } 384 } 385 386 /** 387 * Create an XML element to represent this Entry. This member has to remain 388 * synchronized with the detailed DTD in xml/DTD/consist-roster-config.dtd. 389 * 390 * @return Contents in a JDOM Element 391 */ 392 org.jdom2.Element store() { 393 org.jdom2.Element e = new org.jdom2.Element("consist"); 394 e.setAttribute("id", getId()); 395 e.setAttribute("consistNumber", getConsistNumber()); 396 e.setAttribute("roadNumber", getRoadNumber()); 397 e.setAttribute("roadName", getRoadName()); 398 e.setAttribute("model", getModel()); 399 e.setAttribute("comment", getComment()); 400 401 org.jdom2.Element loco1 = new org.jdom2.Element("loco"); 402 loco1.setAttribute("locoName", "lead"); 403 loco1.setAttribute("dccLocoAddress", getLoco1DccAddress()); 404 loco1.setAttribute("longAddress", isLoco1LongAddress() ? "yes" : "no"); 405 loco1.setAttribute("locoDir", getLoco1Direction()); 406 e.addContent(loco1); 407 408 org.jdom2.Element loco2 = new org.jdom2.Element("loco"); 409 loco2.setAttribute("locoName", "rear"); 410 loco2.setAttribute("dccLocoAddress", getLoco2DccAddress()); 411 loco2.setAttribute("longAddress", isLoco2LongAddress() ? "yes" : "no"); 412 loco2.setAttribute("locoDir", getLoco2Direction()); 413 e.addContent(loco2); 414 415 if (!getLoco3DccAddress().isEmpty()) { 416 org.jdom2.Element loco3 = new org.jdom2.Element("loco"); 417 loco3.setAttribute("locoName", "mid"); 418 loco3.setAttribute("locoMidNumber", "1"); 419 loco3.setAttribute("dccLocoAddress", getLoco3DccAddress()); 420 loco3.setAttribute("longAddress", isLoco3LongAddress() ? "yes" : "no"); 421 loco3.setAttribute("locoDir", getLoco3Direction()); 422 e.addContent(loco3); 423 } 424 425 if (!getLoco4DccAddress().isEmpty()) { 426 org.jdom2.Element loco4 = new org.jdom2.Element("loco"); 427 loco4.setAttribute("locoName", "mid"); 428 loco4.setAttribute("locoMidNumber", "2"); 429 loco4.setAttribute("dccLocoAddress", getLoco4DccAddress()); 430 loco4.setAttribute("longAddress", isLoco4LongAddress() ? "yes" : "no"); 431 loco4.setAttribute("locoDir", getLoco4Direction()); 432 e.addContent(loco4); 433 } 434 435 if (!getLoco5DccAddress().isEmpty()) { 436 org.jdom2.Element loco5 = new org.jdom2.Element("loco"); 437 loco5.setAttribute("locoName", "mid"); 438 loco5.setAttribute("locoMidNumber", "3"); 439 loco5.setAttribute("dccLocoAddress", getLoco5DccAddress()); 440 loco5.setAttribute("longAddress", isLoco5LongAddress() ? "yes" : "no"); 441 loco5.setAttribute("locoDir", getLoco5Direction()); 442 e.addContent(loco5); 443 } 444 445 if (!getLoco6DccAddress().isEmpty()) { 446 org.jdom2.Element loco6 = new org.jdom2.Element("loco"); 447 loco6.setAttribute("locoName", "mid"); 448 loco6.setAttribute("locoMidNumber", "4"); 449 loco6.setAttribute("dccLocoAddress", getLoco6DccAddress()); 450 loco6.setAttribute("longAddress", isLoco6LongAddress() ? "yes" : "no"); 451 loco6.setAttribute("locoDir", getLoco6Direction()); 452 e.addContent(loco6); 453 } 454 455 return e; 456 } 457 458 public String titleString() { 459 return getId(); 460 } 461 462 @Override 463 public String toString() { 464 String out = "[ConsistRosterEntry: " 465 + _id + " " 466 + " " + _consistNumber 467 + " " + _roadName 468 + " " + _roadNumber 469 + " " + _model 470 + " " + _loco1DccAddress 471 + " " + _loco2DccAddress 472 + " " + _loco3DccAddress 473 + " " + _loco4DccAddress 474 + " " + _loco5DccAddress 475 + " " + _loco6DccAddress 476 + " " + _comment 477 + "]"; 478 return out; 479 } 480 481 /** 482 * Prints the roster information. Updated to allow for multiline comment 483 * field. Created separate write statements for text and line feeds to work 484 * around the HardcopyWriter bug that misplaces borders. 485 * @param w stream to printer 486 */ 487 public void printEntry(Writer w) { 488 if (!(w instanceof HardcopyWriter)){ 489 throw new IllegalArgumentException("Writer is not an instance of HardcopyWriter"); 490 } 491 try { 492 String indent = " "; 493 int indentWidth = indent.length(); 494 HardcopyWriter ww = (HardcopyWriter) w; 495 int textSpace = ww.getCharactersPerLine() - indentWidth - 1; 496 String newLine = "\n"; 497 498 w.write(newLine, 0, 1); 499 String s = " ID: " + _id; 500 w.write(s, 0, s.length()); 501 502 if (!(_consistNumber.isEmpty())) { 503 w.write(newLine, 0, 1); 504 s = " Consist number: " + _consistNumber; 505 w.write(s, 0, s.length()); 506 } 507 if (!(_roadName.isEmpty())) { 508 w.write(newLine, 0, 1); 509 s = " Road name: " + _roadName; 510 w.write(s, 0, s.length()); 511 } 512 if (!(_roadNumber.isEmpty())) { 513 w.write(newLine, 0, 1); 514 s = " Road number: " + _roadNumber; 515 w.write(s, 0, s.length()); 516 } 517 if (!(_model.isEmpty())) { 518 w.write(newLine, 0, 1); 519 s = " Model: " + _model; 520 w.write(s, 0, s.length()); 521 } 522 if (!(_loco1DccAddress.isEmpty())) { 523 w.write(newLine, 0, 1); 524 s = " Lead Address: " + _loco1DccAddress + " " + _loco1Direction; 525 w.write(s, 0, s.length()); 526 } 527 if (!(_loco2DccAddress.isEmpty())) { 528 w.write(newLine, 0, 1); 529 s = " Rear Address: " + _loco2DccAddress + " " + _loco2Direction; 530 w.write(s, 0, s.length()); 531 } 532 if (!(_loco3DccAddress.isEmpty())) { 533 w.write(newLine, 0, 1); 534 s = " Mid1 Address: " + _loco3DccAddress + " " + _loco3Direction; 535 w.write(s, 0, s.length()); 536 } 537 if (!(_loco4DccAddress.isEmpty())) { 538 w.write(newLine, 0, 1); 539 s = " Mid2 Address: " + _loco4DccAddress + " " + _loco4Direction; 540 w.write(s, 0, s.length()); 541 } 542 if (!(_loco5DccAddress.isEmpty())) { 543 w.write(newLine, 0, 1); 544 s = " Mid3 Address: " + _loco5DccAddress + " " + _loco5Direction; 545 w.write(s, 0, s.length()); 546 } 547 if (!(_loco6DccAddress.isEmpty())) { 548 w.write(newLine, 0, 1); 549 s = " Mid4 Address: " + _loco6DccAddress + " " + _loco6Direction; 550 w.write(s, 0, s.length()); 551 } 552 553 // If there is a comment field, then wrap it using the new 554 // wrapComment method and print it 555 if (!(_comment.isEmpty())) { 556 Vector<String> commentVector = wrapComment(_comment, textSpace); 557 558 // Now have a vector of text pieces and line feeds that will all 559 // fit in the allowed space. Print each piece, prefixing the 560 // first one 561 // with the label and indenting any remainding. 562 int k = 0; 563 w.write(newLine, 0, 1); 564 s = " Comment: " 565 + commentVector.elementAt(k); 566 w.write(s, 0, s.length()); 567 k++; 568 while (k < commentVector.size()) { 569 String token = commentVector.elementAt(k); 570 if (!token.equals("\n")) { 571 s = indent + token; 572 } else { 573 s = token; 574 } 575 w.write(s, 0, s.length()); 576 k++; 577 } 578 } 579 w.write(newLine, 0, 1); 580 } catch (IOException e) { 581 log.error("Error printing ConsistRosterEntry", e); 582 } 583 } 584 585 /** 586 * Take a String comment field and perform line wrapping on it. String must 587 * be non-null and may or may not have \n characters embedded. textSpace is 588 * the width of the space to print for wrapping purposes. The comment is 589 * wrapped on a word wrap basis 590 * 591 * This is exactly the same as RosterEntry.wrapComment 592 * @param comment string comment from consist roster entry 593 * @param textSpace size of space to wrap text into 594 * @return wrap formated comment 595 */ 596 public Vector<String> wrapComment(String comment, int textSpace) { 597 // Tokenize the string using \n to separate the text on mulitple lines 598 // and create a vector to hold the processed text pieces 599 StringTokenizer commentTokens = new StringTokenizer(comment, "\n", true); 600 Vector<String> textVector = new Vector<>(commentTokens.countTokens()); 601 String newLine = "\n"; 602 while (commentTokens.hasMoreTokens()) { 603 String commentToken = commentTokens.nextToken(); 604 int startIndex = 0; 605 int endIndex; 606 // Check each token to see if it needs to have a line wrap. 607 // Get a piece of the token, either the size of the allowed space or 608 // a shorter piece if there isn't enough text to fill the space 609 if (commentToken.length() < startIndex + textSpace) { 610 //the piece will fit. 611 textVector.addElement(commentToken); 612 } else { 613 //Piece too long to fit. Extract a piece the size of the textSpace 614 //and check for farthest right space for word wrapping. 615 if (log.isDebugEnabled()) { 616 log.debug("token: /{}/", commentToken); 617 } 618 while (startIndex < commentToken.length()) { 619 String tokenPiece = commentToken.substring(startIndex, startIndex + textSpace); 620 if (log.isDebugEnabled()) { 621 log.debug("loop: /{}/ {}", tokenPiece, tokenPiece.lastIndexOf(" ")); 622 } 623 if (tokenPiece.lastIndexOf(" ") == -1) { 624 //If no spaces, put the whole piece in the vector and add a line feed, then 625 //increment the startIndex to reposition for extracting next piece 626 textVector.addElement(tokenPiece); 627 textVector.addElement(newLine); 628 startIndex += textSpace; 629 } else { 630 //If there is at least one space, extract up to and including the 631 //last space and put in the vector as well as a line feed 632 endIndex = tokenPiece.lastIndexOf(" ") + 1; 633 log.debug("tokenPiece: /{}/ {} {}", tokenPiece, startIndex, endIndex); 634 textVector.addElement(tokenPiece.substring(0, endIndex)); 635 textVector.addElement(newLine); 636 startIndex += endIndex; 637 } 638 //Check the remaining piece to see if it fits -Loco2rtIndex now points 639 //to the start of the next piece 640 if (commentToken.substring(startIndex).length() < textSpace) { 641 // It will fit so just insert it, otherwise will cycle through the 642 // while loop and the checks above will take care of the remainder. 643 // Line feed is not required as this is the last part of the token. 644 tokenPiece = commentToken.substring(startIndex); 645 textVector.addElement(tokenPiece); 646 startIndex += textSpace; 647 } 648 } 649 } 650 } 651 return textVector; 652 } 653 654 // members to remember all the info 655 protected String _fileName = null; 656 657 protected String _id = ""; 658 protected String _consistNumber = ""; 659 protected String _roadName = ""; 660 protected String _roadNumber = ""; 661 protected String _model = ""; 662 protected String _loco1DccAddress = ""; 663 protected boolean _isLoco1LongAddress = true; 664 protected String _loco1Direction = ""; 665 protected String _loco2DccAddress = ""; 666 protected boolean _isLoco2LongAddress = true; 667 protected String _loco2Direction = ""; 668 protected String _loco3DccAddress = ""; 669 protected boolean _isLoco3LongAddress = true; 670 protected String _loco3Direction = ""; 671 protected String _loco4DccAddress = ""; 672 protected boolean _isLoco4LongAddress = true; 673 protected String _loco4Direction = ""; 674 protected String _loco5DccAddress = ""; 675 protected boolean _isLoco5LongAddress = true; 676 protected String _loco5Direction = ""; 677 protected String _loco6DccAddress = ""; 678 protected boolean _isLoco6LongAddress = true; 679 protected String _loco6Direction = ""; 680 681 protected String _comment = ""; 682 683 // initialize logging 684 private final static Logger log = LoggerFactory.getLogger(NceConsistRosterEntry.class); 685 686}