001package jmri.jmrit.operations.trains.trainbuilder; 002 003import com.fasterxml.jackson.databind.util.StdDateFormat; 004 005import java.awt.*; 006import java.io.PrintWriter; 007import java.text.MessageFormat; 008import java.text.SimpleDateFormat; 009import java.util.*; 010import java.util.List; 011 012import javax.swing.JLabel; 013 014import jmri.InstanceManager; 015import jmri.jmrit.operations.locations.*; 016import jmri.jmrit.operations.locations.divisions.DivisionManager; 017import jmri.jmrit.operations.rollingstock.RollingStock; 018import jmri.jmrit.operations.rollingstock.cars.*; 019import jmri.jmrit.operations.rollingstock.engines.*; 020import jmri.jmrit.operations.routes.RouteLocation; 021import jmri.jmrit.operations.setup.Control; 022import jmri.jmrit.operations.setup.Setup; 023import jmri.jmrit.operations.trains.*; 024import jmri.util.ColorUtil; 025import jmri.util.davidflanagan.HardcopyWriter; 026 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030/** 031 * Common routines for trains 032 * 033 * @author Daniel Boudreau (C) Copyright 2008, 2009, 2010, 2011, 2012, 2013, 034 * 2021, 2025 035 */ 036public class TrainCommon { 037 038 protected String tab = tabString("", Setup.getManifestTabLength()); // NOI18N 039 protected static final String NEW_LINE = "\n"; // NOI18N 040 public static final String SPACE = " "; 041 public static final String BLANK_LINE = " "; 042 protected static final char HORIZONTAL_LINE_CHAR = '-'; 043 protected static final String BUILD_REPORT_CHAR = "-"; 044 public static final String HYPHEN = "-"; 045 protected static final char VERTICAL_LINE_CHAR = '|'; 046 protected static final String TEXT_COLOR_START = "<FONT color=\""; 047 protected static final String TEXT_COLOR_DONE = "\">"; 048 protected static final String TEXT_COLOR_END = "</FONT>"; 049 protected static final String TEXT_BOLD = "<b>"; 050 protected static final String TEXT_BOLD_END = "</b>"; 051 protected static final String TEXT_SIZE_START = "<FONT size=\""; 052 protected static final String TEXT_SIZE_DONE = "\">"; 053 protected static final String TEXT_SIZE_END = "</FONTSIZE>"; 054 protected static final String TEXT_ITALIC = "<i>"; 055 protected static final String TEXT_ITALIC_END = "</i>"; 056 057 // when true a pick up, when false a set out 058 protected static final boolean PICKUP = true; 059 // when true Manifest, when false switch list 060 protected static final boolean IS_MANIFEST = true; 061 // when true local car move 062 public static final boolean LOCAL = true; 063 // when true engine attribute, when false car 064 protected static final boolean ENGINE = true; 065 // when true, two column table is sorted by track names 066 public static final boolean IS_TWO_COLUMN_TRACK = true; 067 068 protected CarManager carManager = InstanceManager.getDefault(CarManager.class); 069 protected EngineManager engineManager = InstanceManager.getDefault(EngineManager.class); 070 protected LocationManager locationManager = InstanceManager.getDefault(LocationManager.class); 071 072 protected int _order; 073 074 // for switch lists 075 protected boolean _pickupCars; // true when there are pickups 076 protected boolean _dropCars; // true when there are set outs 077 078 /** 079 * Used to generate "Two Column" format for engines. 080 * 081 * @param file Manifest or Switch List File 082 * @param engineList List of engines for this train. 083 * @param rl The RouteLocation being printed. 084 * @param isManifest True if manifest, false if switch list. 085 */ 086 protected void blockLocosTwoColumn(PrintWriter file, List<Engine> engineList, RouteLocation rl, 087 boolean isManifest) { 088 if (isThereWorkAtLocation(null, engineList, rl)) { 089 printEngineHeader(file, isManifest, !IS_TWO_COLUMN_TRACK); 090 } 091 int lineLength = getLineLength(isManifest); 092 for (Engine engine : engineList) { 093 if (engine.getRouteLocation() == rl && !engine.getTrackName().equals(Engine.NONE)) { 094 String pullText = padAndTruncate(pickupEngine(engine, !IS_TWO_COLUMN_TRACK).trim(), lineLength / 2); 095 pullText = formatColorString(pullText, Setup.getPickupEngineColor()); 096 String s = pullText + VERTICAL_LINE_CHAR + tabString("", lineLength / 2 - 1); 097 addLine(file, s); 098 } 099 if (engine.getRouteDestination() == rl) { 100 String dropText = padAndTruncate(dropEngine(engine, !IS_TWO_COLUMN_TRACK).trim(), lineLength / 2 - 1); 101 dropText = formatColorString(dropText, Setup.getDropEngineColor()); 102 String s = tabString("", lineLength / 2) + VERTICAL_LINE_CHAR + dropText; 103 addLine(file, s); 104 } 105 } 106 } 107 108 /** 109 * Produces a two column format for engine pick ups and set outs. Sorted by 110 * track blocking order. Track name in header format, track name removed 111 * from format. This routine is used to generate the "Two Column by Track" 112 * format for locomotives. 113 * 114 * @param file Manifest or switch list File 115 * @param engineList List of engines for this train 116 * @param rl The RouteLocation being printed 117 * @param isManifest True if manifest, false if switch list. 118 */ 119 protected void blockLocosByTrackNameTwoColumn(PrintWriter file, List<Engine> engineList, RouteLocation rl, 120 boolean isManifest) { 121 index = 0; 122 List<Track> tracks = rl.getLocation().getTracksByBlockingOrderList(null); 123 List<String> trackNames = new ArrayList<>(); 124 if (isThereWorkAtLocation(null, engineList, rl)) { 125 printEngineHeader(file, isManifest, IS_TWO_COLUMN_TRACK); 126 } 127 int lineLength = getLineLength(isManifest); 128 for (Track track : tracks) { 129 String trackName = track.getSplitName(); 130 if (trackNames.contains(trackName)) { 131 continue; 132 } 133 for (Engine engine : engineList) { 134 if (engine.getTrack() != null && 135 engine.getRouteLocation() == rl && 136 trackName.equals(engine.getSplitTrackName())) { 137 if (!trackNames.contains(trackName)) { 138 printTrackNameHeader(file, trackName, isManifest); 139 } 140 trackNames.add(trackName); // use a track name once 141 String pullText = padAndTruncate(pickupEngine(engine, IS_TWO_COLUMN_TRACK).trim(), lineLength / 2); 142 pullText = formatColorString(pullText, Setup.getPickupEngineColor()); 143 String s = pullText + VERTICAL_LINE_CHAR + tabString("", lineLength / 2 - 1); 144 addLine(file, s); 145 } 146 if (engine.getRouteDestination() == rl && 147 trackName.equals(engine.getSplitDestinationTrackName())) { 148 if (!trackNames.contains(trackName)) { 149 printTrackNameHeader(file, trackName, isManifest); 150 } 151 trackNames.add(trackName); // use a track name once 152 String dropText = 153 padAndTruncate(dropEngine(engine, IS_TWO_COLUMN_TRACK).trim(), lineLength / 2 - 1); 154 dropText = formatColorString(dropText, Setup.getDropEngineColor()); 155 String s = tabString("", lineLength / 2) + VERTICAL_LINE_CHAR + dropText; 156 addLine(file, s); 157 } 158 } 159 } 160 } 161 162 /** 163 * Adds a list of locomotive pick ups for the route location to the output 164 * file. Used to generate "Standard" format. 165 * 166 * @param file Manifest or Switch List File 167 * @param engineList List of engines for this train. 168 * @param rl The RouteLocation being printed. 169 * @param isManifest True if manifest, false if switch list 170 */ 171 protected void pickupEngines(PrintWriter file, List<Engine> engineList, RouteLocation rl, boolean isManifest) { 172 boolean printHeader = Setup.isPrintHeadersEnabled(); 173 for (Engine engine : engineList) { 174 if (engine.getRouteLocation() == rl && !engine.getTrackName().equals(Engine.NONE)) { 175 if (printHeader) { 176 printPickupEngineHeader(file, isManifest); 177 printHeader = false; 178 } 179 pickupEngine(file, engine, isManifest); 180 } 181 } 182 } 183 184 private void pickupEngine(PrintWriter file, Engine engine, boolean isManifest) { 185 engine.setOrder(++_order + HYPHEN + engine.getRouteDestination().getSequenceNumber()); 186 StringBuffer buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getPickupEnginePrefix(), 187 isManifest ? Setup.getManifestPrefixLength() : Setup.getSwitchListPrefixLength())); 188 String[] format = Setup.getPickupEngineMessageFormat(); 189 for (String attribute : format) { 190 String s = getEngineAttribute(engine, attribute, PICKUP); 191 if (!checkStringLength(buf.toString() + s, isManifest)) { 192 addLine(file, buf, Setup.getPickupEngineColor()); 193 buf = new StringBuffer(tab); // new line 194 } 195 buf.append(s); 196 } 197 addLine(file, buf, Setup.getPickupEngineColor()); 198 } 199 200 /** 201 * Adds a list of locomotive drops for the route location to the output 202 * file. Used to generate "Standard" format. 203 * 204 * @param file Manifest or Switch List File 205 * @param engineList List of engines for this train. 206 * @param rl The RouteLocation being printed. 207 * @param isManifest True if manifest, false if switch list 208 */ 209 protected void dropEngines(PrintWriter file, List<Engine> engineList, RouteLocation rl, boolean isManifest) { 210 boolean printHeader = Setup.isPrintHeadersEnabled(); 211 for (Engine engine : engineList) { 212 if (engine.getRouteDestination() == rl) { 213 if (printHeader) { 214 printDropEngineHeader(file, isManifest); 215 printHeader = false; 216 } 217 dropEngine(file, engine, isManifest); 218 } 219 } 220 } 221 222 private void dropEngine(PrintWriter file, Engine engine, boolean isManifest) { 223 StringBuffer buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getDropEnginePrefix(), 224 isManifest ? Setup.getManifestPrefixLength() : Setup.getSwitchListPrefixLength())); 225 String[] format = Setup.getDropEngineMessageFormat(); 226 for (String attribute : format) { 227 String s = getEngineAttribute(engine, attribute, !PICKUP); 228 if (!checkStringLength(buf.toString() + s, isManifest)) { 229 addLine(file, buf, Setup.getDropEngineColor()); 230 buf = new StringBuffer(tab); // new line 231 } 232 buf.append(s); 233 } 234 addLine(file, buf, Setup.getDropEngineColor()); 235 } 236 237 /** 238 * Returns the pick up string for a loco. Useful for frames like the train 239 * conductor, yardmaster, and two column format. 240 * 241 * @param engine The Engine. 242 * @param isTwoColumnTrack when true two column track by name. 243 * @return engine pick up string 244 */ 245 public String pickupEngine(Engine engine, boolean isTwoColumnTrack) { 246 String[] format = Setup.getPickupEngineMessageFormat(); 247 if (isTwoColumnTrack) { 248 format = Setup.getPickupEngineTwoColumnByTrackMessageFormat(); 249 } 250 StringBuilder builder = new StringBuilder(); 251 for (String attribute : format) { 252 builder.append(getEngineAttribute(engine, attribute, PICKUP)); 253 } 254 return builder.toString(); 255 } 256 257 /** 258 * Returns the drop string for a loco. Useful for frames like the train 259 * conductor and yardmaster. 260 * 261 * @param engine The Engine. 262 * @param isTwoColumnTrack when true two column track by name. 263 * @return engine drop string 264 */ 265 public String dropEngine(Engine engine, boolean isTwoColumnTrack) { 266 String[] format = Setup.getDropEngineMessageFormat(); 267 if (isTwoColumnTrack) { 268 format = Setup.getDropEngineTwoColumnByTrackMessageFormat(); 269 } 270 StringBuilder builder = new StringBuilder(); 271 for (String attribute : format) { 272 builder.append(getEngineAttribute(engine, attribute, !PICKUP)); 273 } 274 return builder.toString(); 275 } 276 277 // the next three booleans are used to limit the header to once per location 278 boolean _printPickupHeader = true; 279 boolean _printSetoutHeader = true; 280 boolean _printLocalMoveHeader = true; 281 282 /** 283 * Block cars by track, then pick up and set out for each location in a 284 * train's route. This routine is used for the "Standard" format. 285 * 286 * @param file Manifest or switch list File 287 * @param train The train being printed. 288 * @param carList List of cars for this train 289 * @param rl The RouteLocation being printed 290 * @param printHeader True if new location. 291 * @param isManifest True if manifest, false if switch list. 292 */ 293 protected void blockCarsByTrack(PrintWriter file, Train train, List<Car> carList, RouteLocation rl, 294 boolean printHeader, boolean isManifest) { 295 if (printHeader) { 296 _printPickupHeader = true; 297 _printSetoutHeader = true; 298 _printLocalMoveHeader = true; 299 } 300 List<Track> tracks = rl.getLocation().getTracksByNameList(null); 301 List<String> trackNames = new ArrayList<>(); 302 clearUtilityCarTypes(); // list utility cars by quantity 303 for (Track track : tracks) { 304 if (trackNames.contains(track.getSplitName())) { 305 continue; 306 } 307 trackNames.add(track.getSplitName()); // use a track name once 308 309 // car pick ups 310 blockCarsPickups(file, train, carList, rl, track, isManifest); 311 312 // now do car set outs and local moves 313 // group local moves first? 314 blockCarsSetoutsAndMoves(file, train, carList, rl, track, isManifest, false, 315 Setup.isGroupCarMovesEnabled()); 316 // set outs or both 317 blockCarsSetoutsAndMoves(file, train, carList, rl, track, isManifest, true, 318 !Setup.isGroupCarMovesEnabled()); 319 320 if (!Setup.isSortByTrackNameEnabled()) { 321 break; // done 322 } 323 } 324 } 325 326 private void blockCarsPickups(PrintWriter file, Train train, List<Car> carList, RouteLocation rl, 327 Track track, boolean isManifest) { 328 for (RouteLocation rld : train.getTrainBlockingOrder()) { 329 for (Car car : carList) { 330 if (Setup.isSortByTrackNameEnabled() && 331 !track.getSplitName().equals(car.getSplitTrackName())) { 332 continue; 333 } 334 // Block cars 335 // caboose or FRED is placed at end of the train 336 // passenger cars are already blocked in the car list 337 // passenger cars with negative block numbers are placed at 338 // the front of the train, positive numbers at the end of 339 // the train. 340 if (isNextCar(car, rl, rld)) { 341 // determine if pick up header is needed 342 printPickupCarHeader(file, car, isManifest, !IS_TWO_COLUMN_TRACK); 343 344 // use truncated format if there's a switch list 345 boolean isTruncate = Setup.isPrintTruncateManifestEnabled() && 346 rl.getLocation().isSwitchListEnabled(); 347 348 if (car.isUtility()) { 349 pickupUtilityCars(file, carList, car, isTruncate, isManifest); 350 } else if (isManifest && isTruncate) { 351 pickUpCarTruncated(file, car, isManifest); 352 } else { 353 pickUpCar(file, car, isManifest); 354 } 355 _pickupCars = true; 356 } 357 } 358 } 359 } 360 361 private void blockCarsSetoutsAndMoves(PrintWriter file, Train train, List<Car> carList, RouteLocation rl, 362 Track track, boolean isManifest, boolean isSetout, boolean isLocalMove) { 363 for (Car car : carList) { 364 if (!car.isLocalMove() && isSetout || car.isLocalMove() && isLocalMove) { 365 if (Setup.isSortByTrackNameEnabled() && 366 car.getRouteLocation() != null && 367 car.getRouteDestination() == rl) { 368 // must sort local moves by car's destination track name and not car's track name 369 // sorting by car's track name fails if there are "similar" location names. 370 if (!track.getSplitName().equals(car.getSplitDestinationTrackName())) { 371 continue; 372 } 373 } 374 if (car.getRouteDestination() == rl && car.getDestinationTrack() != null) { 375 // determine if drop or move header is needed 376 printDropOrMoveCarHeader(file, car, isManifest, !IS_TWO_COLUMN_TRACK); 377 378 // use truncated format if there's a switch list 379 boolean isTruncate = Setup.isPrintTruncateManifestEnabled() && 380 rl.getLocation().isSwitchListEnabled() && 381 !train.isLocalSwitcher(); 382 383 if (car.isUtility()) { 384 setoutUtilityCars(file, carList, car, isTruncate, isManifest); 385 } else if (isManifest && isTruncate) { 386 truncatedDropCar(file, car, isManifest); 387 } else { 388 dropCar(file, car, isManifest); 389 } 390 _dropCars = true; 391 } 392 } 393 } 394 } 395 396 /** 397 * Used to determine if car is the next to be processed when producing 398 * Manifests or Switch Lists. Caboose or FRED is placed at end of the train 399 * unless they are also passenger cars. Passenger cars are already blocked 400 * in the car list. Passenger cars with negative block numbers are placed at 401 * the front of the train, positive numbers at the end of the train. Note 402 * that a car in train doesn't have a track assignment. 403 * 404 * @param car the car being tested 405 * @param rl when in train's route the car is being pulled 406 * @param rld the destination being tested 407 * @return true if this car is the next one to be processed 408 */ 409 public static boolean isNextCar(Car car, RouteLocation rl, RouteLocation rld) { 410 return isNextCar(car, rl, rld, false); 411 } 412 413 public static boolean isNextCar(Car car, RouteLocation rl, RouteLocation rld, boolean isIgnoreTrack) { 414 Train train = car.getTrain(); 415 if (train != null && 416 (car.getTrack() != null || isIgnoreTrack) && 417 car.getRouteLocation() == rl && 418 (rld == car.getRouteDestination() && 419 !car.isCaboose() && 420 !car.hasFred() && 421 !car.isPassenger() || 422 car.isPassenger() && 423 car.getBlocking() < 0 && 424 rld == train.getRoute().getBlockingLocationFrontOfTrain() || 425 (car.isCaboose() && !car.isPassenger() || 426 car.hasFred() && !car.isPassenger() || 427 car.isPassenger() && car.getBlocking() >= 0) && 428 rld == train.getRoute().getBlockingLocationRearOfTrain())) { 429 return true; 430 } 431 return false; 432 } 433 434 private void printPickupCarHeader(PrintWriter file, Car car, boolean isManifest, boolean isTwoColumnTrack) { 435 if (_printPickupHeader && !car.isLocalMove()) { 436 printPickupCarHeader(file, isManifest, !IS_TWO_COLUMN_TRACK); 437 _printPickupHeader = false; 438 // check to see if the other headers are needed. If 439 // they are identical, not needed 440 if (getPickupCarHeader(isManifest, !IS_TWO_COLUMN_TRACK) 441 .equals(getDropCarHeader(isManifest, !IS_TWO_COLUMN_TRACK))) { 442 _printSetoutHeader = false; 443 } 444 if (getPickupCarHeader(isManifest, !IS_TWO_COLUMN_TRACK) 445 .equals(getLocalMoveHeader(isManifest))) { 446 _printLocalMoveHeader = false; 447 } 448 } 449 } 450 451 private void printDropOrMoveCarHeader(PrintWriter file, Car car, boolean isManifest, boolean isTwoColumnTrack) { 452 if (_printSetoutHeader && !car.isLocalMove()) { 453 printDropCarHeader(file, isManifest, !IS_TWO_COLUMN_TRACK); 454 _printSetoutHeader = false; 455 // check to see if the other headers are needed. If they 456 // are identical, not needed 457 if (getPickupCarHeader(isManifest, !IS_TWO_COLUMN_TRACK) 458 .equals(getDropCarHeader(isManifest, !IS_TWO_COLUMN_TRACK))) { 459 _printPickupHeader = false; 460 } 461 if (getDropCarHeader(isManifest, !IS_TWO_COLUMN_TRACK).equals(getLocalMoveHeader(isManifest))) { 462 _printLocalMoveHeader = false; 463 } 464 } 465 if (_printLocalMoveHeader && car.isLocalMove()) { 466 printLocalCarMoveHeader(file, isManifest); 467 _printLocalMoveHeader = false; 468 // check to see if the other headers are needed. If they 469 // are identical, not needed 470 if (getPickupCarHeader(isManifest, !IS_TWO_COLUMN_TRACK) 471 .equals(getLocalMoveHeader(isManifest))) { 472 _printPickupHeader = false; 473 } 474 if (getDropCarHeader(isManifest, !IS_TWO_COLUMN_TRACK).equals(getLocalMoveHeader(isManifest))) { 475 _printSetoutHeader = false; 476 } 477 } 478 } 479 480 /** 481 * Produces a two column format for car pick ups and set outs. Sorted by 482 * track and then by blocking order. This routine is used for the "Two 483 * Column" format. 484 * 485 * @param file Manifest or switch list File 486 * @param train The train 487 * @param carList List of cars for this train 488 * @param rl The RouteLocation being printed 489 * @param printHeader True if new location. 490 * @param isManifest True if manifest, false if switch list. 491 */ 492 protected void blockCarsTwoColumn(PrintWriter file, Train train, List<Car> carList, RouteLocation rl, 493 boolean printHeader, boolean isManifest) { 494 index = 0; 495 int lineLength = getLineLength(isManifest); 496 List<Track> tracks = rl.getLocation().getTracksByNameList(null); 497 List<String> trackNames = new ArrayList<>(); 498 clearUtilityCarTypes(); // list utility cars by quantity 499 if (printHeader) { 500 printCarHeader(file, isManifest, !IS_TWO_COLUMN_TRACK); 501 } 502 for (Track track : tracks) { 503 if (trackNames.contains(track.getSplitName())) { 504 continue; 505 } 506 trackNames.add(track.getSplitName()); // use a track name once 507 // block car pick ups 508 for (RouteLocation rld : train.getTrainBlockingOrder()) { 509 for (int k = 0; k < carList.size(); k++) { 510 Car car = carList.get(k); 511 // block cars 512 // caboose or FRED is placed at end of the train 513 // passenger cars are already blocked in the car list 514 // passenger cars with negative block numbers are placed at 515 // the front of the train, positive numbers at the end of 516 // the train. 517 if (isNextCar(car, rl, rld)) { 518 if (Setup.isSortByTrackNameEnabled() && 519 !track.getSplitName().equals(car.getSplitTrackName())) { 520 continue; 521 } 522 _pickupCars = true; 523 String s; 524 if (car.isUtility()) { 525 s = pickupUtilityCars(carList, car, isManifest, !IS_TWO_COLUMN_TRACK); 526 if (s == null) { 527 continue; 528 } 529 s = s.trim(); 530 } else { 531 s = pickupCar(car, isManifest, !IS_TWO_COLUMN_TRACK).trim(); 532 } 533 s = padAndTruncate(s, lineLength / 2); 534 if (car.isLocalMove()) { 535 s = formatColorString(s, Setup.getLocalColor()); 536 String sl = appendSetoutString(s, carList, car.getRouteDestination(), car, isManifest, 537 !IS_TWO_COLUMN_TRACK); 538 // check for utility car, and local route with two 539 // or more locations 540 if (!sl.equals(s)) { 541 s = sl; 542 carList.remove(car); // done with this car, remove from list 543 k--; 544 } else { 545 s = padAndTruncate(s + VERTICAL_LINE_CHAR, getLineLength(isManifest)); 546 } 547 } else { 548 s = formatColorString(s, Setup.getPickupColor()); 549 s = appendSetoutString(s, carList, rl, true, isManifest, !IS_TWO_COLUMN_TRACK); 550 } 551 addLine(file, s); 552 } 553 } 554 } 555 if (!Setup.isSortByTrackNameEnabled()) { 556 break; // done 557 } 558 } 559 while (index < carList.size()) { 560 String s = padString("", lineLength / 2); 561 s = appendSetoutString(s, carList, rl, false, isManifest, !IS_TWO_COLUMN_TRACK); 562 String test = s.trim(); 563 // null line contains | 564 if (test.length() > 1) { 565 addLine(file, s); 566 } 567 } 568 } 569 570 List<Car> doneCars = new ArrayList<>(); 571 572 /** 573 * Produces a two column format for car pick ups and set outs. Sorted by 574 * track and then by destination. Track name in header format, track name 575 * removed from format. This routine is used to generate the "Two Column by 576 * Track" format. 577 * 578 * @param file Manifest or switch list File 579 * @param train The train 580 * @param carList List of cars for this train 581 * @param rl The RouteLocation being printed 582 * @param printHeader True if new location. 583 * @param isManifest True if manifest, false if switch list. 584 */ 585 protected void blockCarsByTrackNameTwoColumn(PrintWriter file, Train train, List<Car> carList, RouteLocation rl, 586 boolean printHeader, boolean isManifest) { 587 index = 0; 588 List<Track> tracks = rl.getLocation().getTracksByBlockingOrderList(null); 589 List<String> trackNames = new ArrayList<>(); 590 doneCars.clear(); 591 clearUtilityCarTypes(); // list utility cars by quantity 592 if (printHeader) { 593 printCarHeader(file, isManifest, IS_TWO_COLUMN_TRACK); 594 } 595 for (Track track : tracks) { 596 String trackName = track.getSplitName(); 597 if (trackNames.contains(trackName)) { 598 continue; 599 } 600 // block car pick ups 601 for (RouteLocation rld : train.getTrainBlockingOrder()) { 602 for (Car car : carList) { 603 if (car.getTrack() != null && 604 car.getRouteLocation() == rl && 605 trackName.equals(car.getSplitTrackName()) && 606 ((car.getRouteDestination() == rld && !car.isCaboose() && !car.hasFred()) || 607 (rld == train.getTrainTerminatesRouteLocation() && 608 (car.isCaboose() || car.hasFred())))) { 609 if (!trackNames.contains(trackName)) { 610 printTrackNameHeader(file, trackName, isManifest); 611 } 612 trackNames.add(trackName); // use a track name once 613 _pickupCars = true; 614 String s; 615 if (car.isUtility()) { 616 s = pickupUtilityCars(carList, car, isManifest, IS_TWO_COLUMN_TRACK); 617 if (s == null) { 618 continue; 619 } 620 s = s.trim(); 621 } else { 622 s = pickupCar(car, isManifest, IS_TWO_COLUMN_TRACK).trim(); 623 } 624 s = padAndTruncate(s, getLineLength(isManifest) / 2); 625 s = formatColorString(s, car.isLocalMove() ? Setup.getLocalColor() : Setup.getPickupColor()); 626 s = appendSetoutString(s, trackName, carList, rl, isManifest, IS_TWO_COLUMN_TRACK); 627 addLine(file, s); 628 } 629 } 630 } 631 for (Car car : carList) { 632 if (!doneCars.contains(car) && 633 car.getRouteDestination() == rl && 634 trackName.equals(car.getSplitDestinationTrackName())) { 635 if (!trackNames.contains(trackName)) { 636 printTrackNameHeader(file, trackName, isManifest); 637 } 638 trackNames.add(trackName); // use a track name once 639 String s = padString("", getLineLength(isManifest) / 2); 640 String so = appendSetoutString(s, carList, rl, car, isManifest, IS_TWO_COLUMN_TRACK); 641 // check for utility car 642 if (so.equals(s)) { 643 continue; 644 } 645 String test = so.trim(); 646 if (test.length() > 1) // null line contains | 647 { 648 addLine(file, so); 649 } 650 } 651 } 652 } 653 } 654 655 protected void printTrackComments(PrintWriter file, RouteLocation rl, List<Car> carList, boolean isManifest) { 656 Location location = rl.getLocation(); 657 if (location != null) { 658 List<Track> tracks = location.getTracksByNameList(null); 659 for (Track track : tracks) { 660 if (isManifest && !track.isPrintManifestCommentEnabled() || 661 !isManifest && !track.isPrintSwitchListCommentEnabled()) { 662 continue; 663 } 664 // any pick ups or set outs to this track? 665 boolean pickup = false; 666 boolean setout = false; 667 for (Car car : carList) { 668 if (car.getRouteLocation() == rl && car.getTrack() != null && car.getTrack() == track) { 669 pickup = true; 670 } 671 if (car.getRouteDestination() == rl && 672 car.getDestinationTrack() != null && 673 car.getDestinationTrack() == track) { 674 setout = true; 675 } 676 } 677 // print the appropriate comment if there's one 678 if (pickup && setout && !track.getCommentBothWithColor().equals(Track.NONE)) { 679 newLine(file, track.getCommentBothWithColor()); 680 } else if (pickup && !setout && !track.getCommentPickupWithColor().equals(Track.NONE)) { 681 newLine(file, track.getCommentPickupWithColor()); 682 } else if (!pickup && setout && !track.getCommentSetoutWithColor().equals(Track.NONE)) { 683 newLine(file, track.getCommentSetoutWithColor()); 684 } 685 } 686 } 687 } 688 689 protected void setPickupAndSetoutTimes(Train train, RouteLocation rl, List<RollingStock> list) { 690 String expectedDepartureTime = train.getExpectedDepartureTime(rl, true); 691 for (RollingStock rs : list) { 692 if (rs.getRouteLocation() == rl) { 693 rs.setPickupTime(expectedDepartureTime); 694 } 695 if (rs.getRouteDestination() == rl) { 696 rs.setSetoutTime(expectedDepartureTime); 697 } 698 } 699 700 } 701 702 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST", 703 justification = "Only when exception") 704 public static String getTrainMessage(Train train, RouteLocation rl) { 705 String expectedArrivalTime = train.getExpectedArrivalTime(rl); 706 String routeLocationName = rl.getSplitName(); 707 String msg = ""; 708 String messageFormatText = ""; // the text being formated in case there's an exception 709 try { 710 // Scheduled work at {0} 711 msg = MessageFormat.format(messageFormatText = TrainManifestText 712 .getStringScheduledWork(), 713 new Object[]{routeLocationName, train.getSplitName(), 714 train.getDescription(), rl.getLocation().getDivisionName()}); 715 if (train.isShowArrivalAndDepartureTimesEnabled()) { 716 if (rl == train.getTrainDepartsRouteLocation()) { 717 // Scheduled work at {0}, departure time {1} 718 msg = MessageFormat.format(messageFormatText = TrainManifestText 719 .getStringWorkDepartureTime(), 720 new Object[]{routeLocationName, 721 train.getFormatedDepartureTime(), train.getSplitName(), 722 train.getDescription(), rl.getLocation().getDivisionName()}); 723 } else if (!rl.getDepartureTimeHourMinutes().equals(RouteLocation.NONE) && 724 rl != train.getTrainTerminatesRouteLocation()) { 725 // Scheduled work at {0}, departure time {1} 726 msg = MessageFormat.format(messageFormatText = TrainManifestText 727 .getStringWorkDepartureTime(), 728 new Object[]{routeLocationName, train.getExpectedDepartureTime(rl), 729 train.getSplitName(), train.getDescription(), 730 rl.getLocation().getDivisionName()}); 731 } else if (Setup.isUseDepartureTimeEnabled() && 732 rl != train.getTrainTerminatesRouteLocation() && 733 !expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 734 // Scheduled work at {0}, departure time {1} 735 msg = MessageFormat.format(messageFormatText = TrainManifestText 736 .getStringWorkDepartureTime(), 737 new Object[]{routeLocationName, 738 train.getExpectedDepartureTime(rl), train.getSplitName(), 739 train.getDescription(), rl.getLocation().getDivisionName()}); 740 } else if (!expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 741 // Scheduled work at {0}, arrival time {1} 742 msg = MessageFormat.format(messageFormatText = TrainManifestText 743 .getStringWorkArrivalTime(), 744 new Object[]{routeLocationName, expectedArrivalTime, 745 train.getSplitName(), train.getDescription(), 746 rl.getLocation().getDivisionName()}); 747 } 748 } 749 return msg; 750 } catch (IllegalArgumentException e) { 751 msg = Bundle.getMessage("ErrorIllegalArgument", 752 Bundle.getMessage("TitleManifestText"), e.getLocalizedMessage()) + NEW_LINE + messageFormatText; 753 log.error(msg); 754 log.error("Illegal argument", e); 755 return msg; 756 } 757 } 758 759 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST", 760 justification = "Only when exception") 761 public static String getSwitchListTrainStatus(Train train, RouteLocation rl) { 762 String expectedArrivalTime = train.getExpectedArrivalTime(rl); 763 String msg = ""; 764 String messageFormatText = ""; // the text being formated in case there's an exception 765 try { 766 if (train.isLocalSwitcher()) { 767 // Use Manifest text for local departure 768 // Scheduled work at {0}, departure time {1} 769 msg = MessageFormat.format(messageFormatText = TrainManifestText.getStringWorkDepartureTime(), 770 new Object[]{splitString(train.getTrainDepartsName()), train.getFormatedDepartureTime(), 771 train.getSplitName(), train.getDescription(), 772 rl.getLocation().getDivisionName()}); 773 } else if (rl == train.getTrainDepartsRouteLocation()) { 774 // Departs {0} {1}bound at {2} 775 msg = MessageFormat.format(messageFormatText = TrainSwitchListText.getStringDepartsAt(), 776 new Object[]{splitString(train.getTrainDepartsName()), rl.getTrainDirectionString(), 777 train.getFormatedDepartureTime()}); 778 } else if (Setup.isUseSwitchListDepartureTimeEnabled() && 779 rl != train.getTrainTerminatesRouteLocation() && 780 !train.isTrainEnRoute()) { 781 // Departs {0} at {1} expected arrival {2}, arrives {3}bound 782 msg = MessageFormat.format( 783 messageFormatText = TrainSwitchListText.getStringDepartsAtExpectedArrival(), 784 new Object[]{splitString(rl.getName()), 785 train.getExpectedDepartureTime(rl), expectedArrivalTime, 786 rl.getTrainDirectionString()}); 787 } else if (Setup.isUseSwitchListDepartureTimeEnabled() && 788 rl == train.getCurrentRouteLocation() && 789 rl != train.getTrainTerminatesRouteLocation() && 790 !rl.getDepartureTimeHourMinutes().equals(RouteLocation.NONE)) { 791 // Departs {0} {1}bound at {2} 792 msg = MessageFormat.format(messageFormatText = TrainSwitchListText.getStringDepartsAt(), 793 new Object[]{splitString(rl.getName()), rl.getTrainDirectionString(), 794 rl.getFormatedDepartureTime()}); 795 } else if (train.isTrainEnRoute()) { 796 if (!expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 797 // Departed {0}, expect to arrive in {1}, arrives {2}bound 798 msg = MessageFormat.format(messageFormatText = TrainSwitchListText.getStringDepartedExpected(), 799 new Object[]{splitString(train.getTrainDepartsName()), expectedArrivalTime, 800 rl.getTrainDirectionString(), train.getCurrentLocationName()}); 801 } 802 } else { 803 // Departs {0} at {1} expected arrival {2}, arrives {3}bound 804 msg = MessageFormat.format( 805 messageFormatText = TrainSwitchListText.getStringDepartsAtExpectedArrival(), 806 new Object[]{splitString(train.getTrainDepartsName()), 807 train.getFormatedDepartureTime(), expectedArrivalTime, 808 rl.getTrainDirectionString()}); 809 } 810 return msg; 811 } catch (IllegalArgumentException e) { 812 msg = Bundle.getMessage("ErrorIllegalArgument", 813 Bundle.getMessage("TitleSwitchListText"), e.getLocalizedMessage()) + NEW_LINE + messageFormatText; 814 log.error(msg); 815 log.error("Illegal argument", e); 816 return msg; 817 } 818 } 819 820 int index = 0; 821 822 /* 823 * Used by two column format. Local moves (pulls and spots) are lined up 824 * when using this format, 825 */ 826 private String appendSetoutString(String s, List<Car> carList, RouteLocation rl, boolean local, boolean isManifest, 827 boolean isTwoColumnTrack) { 828 while (index < carList.size()) { 829 Car car = carList.get(index++); 830 if (local && car.isLocalMove()) { 831 continue; // skip local moves 832 } 833 // car list is already sorted by destination track 834 if (car.getRouteDestination() == rl) { 835 String so = appendSetoutString(s, carList, rl, car, isManifest, isTwoColumnTrack); 836 // check for utility car 837 if (!so.equals(s)) { 838 return so; 839 } 840 } 841 } 842 // no set out for this line 843 return s + VERTICAL_LINE_CHAR + padAndTruncate("", getLineLength(isManifest) / 2 - 1); 844 } 845 846 /* 847 * Used by two column, track names shown in the columns. 848 */ 849 private String appendSetoutString(String s, String trackName, List<Car> carList, RouteLocation rl, 850 boolean isManifest, boolean isTwoColumnTrack) { 851 for (Car car : carList) { 852 if (!doneCars.contains(car) && 853 car.getRouteDestination() == rl && 854 trackName.equals(car.getSplitDestinationTrackName())) { 855 doneCars.add(car); 856 String so = appendSetoutString(s, carList, rl, car, isManifest, isTwoColumnTrack); 857 // check for utility car 858 if (!so.equals(s)) { 859 return so; 860 } 861 } 862 } 863 // no set out for this track 864 return s + VERTICAL_LINE_CHAR + padAndTruncate("", getLineLength(isManifest) / 2 - 1); 865 } 866 867 /* 868 * Appends to string the vertical line character, and the car set out 869 * string. Used in two column format. 870 */ 871 private String appendSetoutString(String s, List<Car> carList, RouteLocation rl, Car car, boolean isManifest, 872 boolean isTwoColumnTrack) { 873 _dropCars = true; 874 String dropText; 875 876 if (car.isUtility()) { 877 dropText = setoutUtilityCars(carList, car, !LOCAL, isManifest, isTwoColumnTrack); 878 if (dropText == null) { 879 return s; // no changes to the input string 880 } 881 } else { 882 dropText = dropCar(car, isManifest, isTwoColumnTrack).trim(); 883 } 884 885 dropText = padAndTruncate(dropText.trim(), getLineLength(isManifest) / 2 - 1); 886 dropText = formatColorString(dropText, car.isLocalMove() ? Setup.getLocalColor() : Setup.getDropColor()); 887 return s + VERTICAL_LINE_CHAR + dropText; 888 } 889 890 /** 891 * Adds the car's pick up string to the output file using the truncated 892 * manifest format 893 * 894 * @param file Manifest or switch list File 895 * @param car The car being printed. 896 * @param isManifest True if manifest, false if switch list. 897 */ 898 protected void pickUpCarTruncated(PrintWriter file, Car car, boolean isManifest) { 899 pickUpCar(file, car, 900 new StringBuffer(padAndTruncateIfNeeded(Setup.getPickupCarPrefix(), Setup.getManifestPrefixLength())), 901 Setup.getPickupTruncatedManifestMessageFormat(), isManifest); 902 } 903 904 /** 905 * Adds the car's pick up string to the output file using the manifest or 906 * switch list format 907 * 908 * @param file Manifest or switch list File 909 * @param car The car being printed. 910 * @param isManifest True if manifest, false if switch list. 911 */ 912 protected void pickUpCar(PrintWriter file, Car car, boolean isManifest) { 913 if (isManifest) { 914 pickUpCar(file, car, 915 new StringBuffer( 916 padAndTruncateIfNeeded(Setup.getPickupCarPrefix(), Setup.getManifestPrefixLength())), 917 Setup.getPickupManifestMessageFormat(), isManifest); 918 } else { 919 pickUpCar(file, car, new StringBuffer( 920 padAndTruncateIfNeeded(Setup.getSwitchListPickupCarPrefix(), Setup.getSwitchListPrefixLength())), 921 Setup.getPickupSwitchListMessageFormat(), isManifest); 922 } 923 } 924 925 private void pickUpCar(PrintWriter file, Car car, StringBuffer buf, String[] format, boolean isManifest) { 926 car.setOrder(++_order + HYPHEN + car.getRouteDestination().getSequenceNumber()); 927 if (car.isLocalMove()) { 928 return; // print nothing local move, see dropCar 929 } 930 for (String attribute : format) { 931 String s = getCarAttribute(car, attribute, PICKUP, !LOCAL); 932 if (!checkStringLength(buf.toString() + s, isManifest)) { 933 addLine(file, buf, Setup.getPickupColor()); 934 buf = new StringBuffer(tab); // new line 935 } 936 buf.append(s); 937 } 938 addLine(file, buf, Setup.getPickupColor()); 939 } 940 941 /** 942 * Returns the pick up car string. Useful for frames like train conductor 943 * and yardmaster. 944 * 945 * @param car The car being printed. 946 * @param isManifest when true use manifest format, when false use 947 * switch list format 948 * @param isTwoColumnTrack True if printing using two column format sorted 949 * by track name. 950 * @return pick up car string 951 */ 952 public String pickupCar(Car car, boolean isManifest, boolean isTwoColumnTrack) { 953 StringBuffer buf = new StringBuffer(); 954 String[] format; 955 if (isManifest && !isTwoColumnTrack) { 956 format = Setup.getPickupManifestMessageFormat(); 957 } else if (!isManifest && !isTwoColumnTrack) { 958 format = Setup.getPickupSwitchListMessageFormat(); 959 } else if (isManifest && isTwoColumnTrack) { 960 format = Setup.getPickupTwoColumnByTrackManifestMessageFormat(); 961 } else { 962 format = Setup.getPickupTwoColumnByTrackSwitchListMessageFormat(); 963 } 964 for (String attribute : format) { 965 buf.append(getCarAttribute(car, attribute, PICKUP, !LOCAL)); 966 } 967 return buf.toString(); 968 } 969 970 /** 971 * Adds the car's set out string to the output file using the truncated 972 * manifest format. Does not print out local moves. Local moves are only 973 * shown on the switch list for that location. 974 * 975 * @param file Manifest or switch list File 976 * @param car The car being printed. 977 * @param isManifest True if manifest, false if switch list. 978 */ 979 protected void truncatedDropCar(PrintWriter file, Car car, boolean isManifest) { 980 // local move? 981 if (car.isLocalMove()) { 982 return; // yes, don't print local moves on train manifest 983 } 984 dropCar(file, car, new StringBuffer(Setup.getDropCarPrefix()), Setup.getDropTruncatedManifestMessageFormat(), 985 false, isManifest); 986 } 987 988 /** 989 * Adds the car's set out string to the output file using the manifest or 990 * switch list format 991 * 992 * @param file Manifest or switch list File 993 * @param car The car being printed. 994 * @param isManifest True if manifest, false if switch list. 995 */ 996 protected void dropCar(PrintWriter file, Car car, boolean isManifest) { 997 boolean isLocal = car.isLocalMove(); 998 if (isManifest) { 999 StringBuffer buf = new StringBuffer( 1000 padAndTruncateIfNeeded(Setup.getDropCarPrefix(), Setup.getManifestPrefixLength())); 1001 String[] format = Setup.getDropManifestMessageFormat(); 1002 if (isLocal) { 1003 buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getLocalPrefix(), Setup.getManifestPrefixLength())); 1004 format = Setup.getLocalManifestMessageFormat(); 1005 } 1006 dropCar(file, car, buf, format, isLocal, isManifest); 1007 } else { 1008 StringBuffer buf = new StringBuffer( 1009 padAndTruncateIfNeeded(Setup.getSwitchListDropCarPrefix(), Setup.getSwitchListPrefixLength())); 1010 String[] format = Setup.getDropSwitchListMessageFormat(); 1011 if (isLocal) { 1012 buf = new StringBuffer( 1013 padAndTruncateIfNeeded(Setup.getSwitchListLocalPrefix(), Setup.getSwitchListPrefixLength())); 1014 format = Setup.getLocalSwitchListMessageFormat(); 1015 } 1016 dropCar(file, car, buf, format, isLocal, isManifest); 1017 } 1018 } 1019 1020 private void dropCar(PrintWriter file, Car car, StringBuffer buf, String[] format, boolean isLocal, 1021 boolean isManifest) { 1022 for (String attribute : format) { 1023 String s = getCarAttribute(car, attribute, !PICKUP, isLocal); 1024 if (!checkStringLength(buf.toString() + s, isManifest)) { 1025 addLine(file, buf, isLocal ? Setup.getLocalColor() : Setup.getDropColor()); 1026 buf = new StringBuffer(tab); // new line 1027 } 1028 buf.append(s); 1029 } 1030 addLine(file, buf, isLocal ? Setup.getLocalColor() : Setup.getDropColor()); 1031 } 1032 1033 /** 1034 * Returns the drop car string. Useful for frames like train conductor and 1035 * yardmaster. 1036 * 1037 * @param car The car being printed. 1038 * @param isManifest when true use manifest format, when false use 1039 * switch list format 1040 * @param isTwoColumnTrack True if printing using two column format. 1041 * @return drop car string 1042 */ 1043 public String dropCar(Car car, boolean isManifest, boolean isTwoColumnTrack) { 1044 StringBuffer buf = new StringBuffer(); 1045 String[] format; 1046 if (isManifest && !isTwoColumnTrack) { 1047 format = Setup.getDropManifestMessageFormat(); 1048 } else if (!isManifest && !isTwoColumnTrack) { 1049 format = Setup.getDropSwitchListMessageFormat(); 1050 } else if (isManifest && isTwoColumnTrack) { 1051 format = Setup.getDropTwoColumnByTrackManifestMessageFormat(); 1052 } else { 1053 format = Setup.getDropTwoColumnByTrackSwitchListMessageFormat(); 1054 } 1055 // TODO the Setup.Location doesn't work correctly for the conductor 1056 // window due to the fact that the car can be in the train and not 1057 // at its starting location. 1058 // Therefore we use the local true to disable it. 1059 boolean local = false; 1060 if (car.getTrack() == null) { 1061 local = true; 1062 } 1063 for (String attribute : format) { 1064 buf.append(getCarAttribute(car, attribute, !PICKUP, local)); 1065 } 1066 return buf.toString(); 1067 } 1068 1069 /** 1070 * Returns the move car string. Useful for frames like train conductor and 1071 * yardmaster. 1072 * 1073 * @param car The car being printed. 1074 * @param isManifest when true use manifest format, when false use switch 1075 * list format 1076 * @return move car string 1077 */ 1078 public String localMoveCar(Car car, boolean isManifest) { 1079 StringBuffer buf = new StringBuffer(); 1080 String[] format; 1081 if (isManifest) { 1082 format = Setup.getLocalManifestMessageFormat(); 1083 } else { 1084 format = Setup.getLocalSwitchListMessageFormat(); 1085 } 1086 for (String attribute : format) { 1087 buf.append(getCarAttribute(car, attribute, !PICKUP, LOCAL)); 1088 } 1089 return buf.toString(); 1090 } 1091 1092 List<String> utilityCarTypes = new ArrayList<>(); 1093 private static final int UTILITY_CAR_COUNT_FIELD_SIZE = 3; 1094 1095 /** 1096 * Add a list of utility cars scheduled for pick up from the route location 1097 * to the output file. The cars are blocked by destination. 1098 * 1099 * @param file Manifest or Switch List File. 1100 * @param carList List of cars for this train. 1101 * @param car The utility car. 1102 * @param isTruncate True if manifest is to be truncated 1103 * @param isManifest True if manifest, false if switch list. 1104 */ 1105 protected void pickupUtilityCars(PrintWriter file, List<Car> carList, Car car, boolean isTruncate, 1106 boolean isManifest) { 1107 // list utility cars by type, track, length, and load 1108 String[] format; 1109 if (isManifest) { 1110 format = Setup.getPickupUtilityManifestMessageFormat(); 1111 } else { 1112 format = Setup.getPickupUtilitySwitchListMessageFormat(); 1113 } 1114 if (isTruncate && isManifest) { 1115 format = Setup.createTruncatedManifestMessageFormat(format); 1116 } 1117 int count = countUtilityCars(format, carList, car, PICKUP); 1118 if (count == 0) { 1119 return; // already printed out this car type 1120 } 1121 pickUpCar(file, car, 1122 new StringBuffer(padAndTruncateIfNeeded(Setup.getPickupCarPrefix(), 1123 isManifest ? Setup.getManifestPrefixLength() : Setup.getSwitchListPrefixLength()) + 1124 SPACE + 1125 padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)), 1126 format, isManifest); 1127 } 1128 1129 /** 1130 * Add a list of utility cars scheduled for drop at the route location to 1131 * the output file. 1132 * 1133 * @param file Manifest or Switch List File. 1134 * @param carList List of cars for this train. 1135 * @param car The utility car. 1136 * @param isTruncate True if manifest is to be truncated 1137 * @param isManifest True if manifest, false if switch list. 1138 */ 1139 protected void setoutUtilityCars(PrintWriter file, List<Car> carList, Car car, boolean isTruncate, 1140 boolean isManifest) { 1141 boolean isLocal = car.isLocalMove(); 1142 StringBuffer buf; 1143 String[] format; 1144 if (isLocal && isManifest) { 1145 buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getLocalPrefix(), Setup.getManifestPrefixLength())); 1146 format = Setup.getLocalUtilityManifestMessageFormat(); 1147 } else if (!isLocal && isManifest) { 1148 buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getDropCarPrefix(), Setup.getManifestPrefixLength())); 1149 format = Setup.getDropUtilityManifestMessageFormat(); 1150 } else if (isLocal && !isManifest) { 1151 buf = new StringBuffer( 1152 padAndTruncateIfNeeded(Setup.getSwitchListLocalPrefix(), Setup.getSwitchListPrefixLength())); 1153 format = Setup.getLocalUtilitySwitchListMessageFormat(); 1154 } else { 1155 buf = new StringBuffer( 1156 padAndTruncateIfNeeded(Setup.getSwitchListDropCarPrefix(), Setup.getSwitchListPrefixLength())); 1157 format = Setup.getDropUtilitySwitchListMessageFormat(); 1158 } 1159 if (isTruncate && isManifest) { 1160 format = Setup.createTruncatedManifestMessageFormat(format); 1161 } 1162 1163 int count = countUtilityCars(format, carList, car, !PICKUP); 1164 if (count == 0) { 1165 return; // already printed out this car type 1166 } 1167 buf.append(SPACE + padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)); 1168 dropCar(file, car, buf, format, isLocal, isManifest); 1169 } 1170 1171 public String pickupUtilityCars(List<Car> carList, Car car, boolean isManifest, boolean isTwoColumnTrack) { 1172 int count = countPickupUtilityCars(carList, car, isManifest); 1173 if (count == 0) { 1174 return null; 1175 } 1176 String[] format; 1177 if (isManifest && !isTwoColumnTrack) { 1178 format = Setup.getPickupUtilityManifestMessageFormat(); 1179 } else if (!isManifest && !isTwoColumnTrack) { 1180 format = Setup.getPickupUtilitySwitchListMessageFormat(); 1181 } else if (isManifest && isTwoColumnTrack) { 1182 format = Setup.getPickupTwoColumnByTrackUtilityManifestMessageFormat(); 1183 } else { 1184 format = Setup.getPickupTwoColumnByTrackUtilitySwitchListMessageFormat(); 1185 } 1186 StringBuffer buf = new StringBuffer(SPACE + padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)); 1187 for (String attribute : format) { 1188 buf.append(getCarAttribute(car, attribute, PICKUP, !LOCAL)); 1189 } 1190 return buf.toString(); 1191 } 1192 1193 public int countPickupUtilityCars(List<Car> carList, Car car, boolean isManifest) { 1194 // list utility cars by type, track, length, and load 1195 String[] format; 1196 if (isManifest) { 1197 format = Setup.getPickupUtilityManifestMessageFormat(); 1198 } else { 1199 format = Setup.getPickupUtilitySwitchListMessageFormat(); 1200 } 1201 return countUtilityCars(format, carList, car, PICKUP); 1202 } 1203 1204 /** 1205 * For the Conductor and Yardmaster windows. 1206 * 1207 * @param carList List of cars for this train. 1208 * @param car The utility car. 1209 * @param isLocal True if local move. 1210 * @param isManifest True if manifest, false if switch list. 1211 * @return A string representing the work of identical utility cars. 1212 */ 1213 public String setoutUtilityCars(List<Car> carList, Car car, boolean isLocal, boolean isManifest) { 1214 return setoutUtilityCars(carList, car, isLocal, isManifest, !IS_TWO_COLUMN_TRACK); 1215 } 1216 1217 protected String setoutUtilityCars(List<Car> carList, Car car, boolean isLocal, boolean isManifest, 1218 boolean isTwoColumnTrack) { 1219 int count = countSetoutUtilityCars(carList, car, isLocal, isManifest); 1220 if (count == 0) { 1221 return null; 1222 } 1223 // list utility cars by type, track, length, and load 1224 String[] format; 1225 if (isLocal && isManifest && !isTwoColumnTrack) { 1226 format = Setup.getLocalUtilityManifestMessageFormat(); 1227 } else if (isLocal && !isManifest && !isTwoColumnTrack) { 1228 format = Setup.getLocalUtilitySwitchListMessageFormat(); 1229 } else if (!isLocal && !isManifest && !isTwoColumnTrack) { 1230 format = Setup.getDropUtilitySwitchListMessageFormat(); 1231 } else if (!isLocal && isManifest && !isTwoColumnTrack) { 1232 format = Setup.getDropUtilityManifestMessageFormat(); 1233 } else if (isManifest && isTwoColumnTrack) { 1234 format = Setup.getDropTwoColumnByTrackUtilityManifestMessageFormat(); 1235 } else { 1236 format = Setup.getDropTwoColumnByTrackUtilitySwitchListMessageFormat(); 1237 } 1238 StringBuffer buf = new StringBuffer(SPACE + padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)); 1239 // TODO the Setup.Location doesn't work correctly for the conductor 1240 // window due to the fact that the car can be in the train and not 1241 // at its starting location. 1242 // Therefore we use the local true to disable it. 1243 if (car.getTrack() == null) { 1244 isLocal = true; 1245 } 1246 for (String attribute : format) { 1247 buf.append(getCarAttribute(car, attribute, !PICKUP, isLocal)); 1248 } 1249 return buf.toString(); 1250 } 1251 1252 public int countSetoutUtilityCars(List<Car> carList, Car car, boolean isLocal, boolean isManifest) { 1253 // list utility cars by type, track, length, and load 1254 String[] format; 1255 if (isLocal && isManifest) { 1256 format = Setup.getLocalUtilityManifestMessageFormat(); 1257 } else if (isLocal && !isManifest) { 1258 format = Setup.getLocalUtilitySwitchListMessageFormat(); 1259 } else if (!isLocal && !isManifest) { 1260 format = Setup.getDropUtilitySwitchListMessageFormat(); 1261 } else { 1262 format = Setup.getDropUtilityManifestMessageFormat(); 1263 } 1264 return countUtilityCars(format, carList, car, !PICKUP); 1265 } 1266 1267 /** 1268 * Scans the car list for utility cars that have the same attributes as the 1269 * car provided. Returns 0 if this car type has already been processed, 1270 * otherwise the number of cars with the same attribute. 1271 * 1272 * @param format Message format. 1273 * @param carList List of cars for this train 1274 * @param car The utility car. 1275 * @param isPickup True if pick up, false if set out. 1276 * @return 0 if the car type has already been processed 1277 */ 1278 protected int countUtilityCars(String[] format, List<Car> carList, Car car, boolean isPickup) { 1279 int count = 0; 1280 // figure out if the user wants to show the car's length 1281 boolean showLength = showUtilityCarLength(format); 1282 // figure out if the user want to show the car's loads 1283 boolean showLoad = showUtilityCarLoad(format); 1284 boolean showLocation = false; 1285 boolean showDestination = false; 1286 String carType = car.getTypeName().split(HYPHEN)[0]; 1287 String carAttributes; 1288 // Note for car pick up: type, id, track name. For set out type, track 1289 // name, id (reversed). 1290 if (isPickup) { 1291 carAttributes = carType + car.getRouteLocationId() + car.getSplitTrackName(); 1292 showDestination = showUtilityCarDestination(format); 1293 if (showDestination) { 1294 carAttributes = carAttributes + car.getRouteDestinationId(); 1295 } 1296 } else { 1297 // set outs and local moves 1298 carAttributes = carType + car.getSplitDestinationTrackName() + car.getRouteDestinationId(); 1299 showLocation = showUtilityCarLocation(format); 1300 if (showLocation && car.getTrack() != null) { 1301 carAttributes = carAttributes + car.getRouteLocationId(); 1302 } 1303 } 1304 if (car.isLocalMove()) { 1305 carAttributes = carAttributes + car.getSplitTrackName(); 1306 } 1307 if (showLength) { 1308 carAttributes = carAttributes + car.getLength(); 1309 } 1310 if (showLoad) { 1311 carAttributes = carAttributes + car.getLoadName(); 1312 } 1313 // have we already done this car type? 1314 if (!utilityCarTypes.contains(carAttributes)) { 1315 utilityCarTypes.add(carAttributes); // don't do this type again 1316 // determine how many cars of this type 1317 for (Car c : carList) { 1318 if (!c.isUtility()) { 1319 continue; 1320 } 1321 String cType = c.getTypeName().split(HYPHEN)[0]; 1322 if (!cType.equals(carType)) { 1323 continue; 1324 } 1325 if (showLength && !c.getLength().equals(car.getLength())) { 1326 continue; 1327 } 1328 if (showLoad && !c.getLoadName().equals(car.getLoadName())) { 1329 continue; 1330 } 1331 if (showLocation && !c.getRouteLocationId().equals(car.getRouteLocationId())) { 1332 continue; 1333 } 1334 if (showDestination && !c.getRouteDestinationId().equals(car.getRouteDestinationId())) { 1335 continue; 1336 } 1337 if (car.isLocalMove() ^ c.isLocalMove()) { 1338 continue; 1339 } 1340 if (isPickup && 1341 c.getRouteLocation() == car.getRouteLocation() && 1342 c.getSplitTrackName().equals(car.getSplitTrackName())) { 1343 count++; 1344 } 1345 if (!isPickup && 1346 c.getRouteDestination() == car.getRouteDestination() && 1347 c.getSplitDestinationTrackName().equals(car.getSplitDestinationTrackName()) && 1348 (c.getSplitTrackName().equals(car.getSplitTrackName()) || !c.isLocalMove())) { 1349 count++; 1350 } 1351 } 1352 } 1353 return count; 1354 } 1355 1356 public void clearUtilityCarTypes() { 1357 utilityCarTypes.clear(); 1358 } 1359 1360 private boolean showUtilityCarLength(String[] mFormat) { 1361 return showUtilityCarAttribute(Setup.LENGTH, mFormat); 1362 } 1363 1364 private boolean showUtilityCarLoad(String[] mFormat) { 1365 return showUtilityCarAttribute(Setup.LOAD, mFormat); 1366 } 1367 1368 private boolean showUtilityCarLocation(String[] mFormat) { 1369 return showUtilityCarAttribute(Setup.LOCATION, mFormat); 1370 } 1371 1372 private boolean showUtilityCarDestination(String[] mFormat) { 1373 return showUtilityCarAttribute(Setup.DESTINATION, mFormat) || 1374 showUtilityCarAttribute(Setup.DEST_TRACK, mFormat); 1375 } 1376 1377 private boolean showUtilityCarAttribute(String string, String[] mFormat) { 1378 for (String s : mFormat) { 1379 if (s.equals(string)) { 1380 return true; 1381 } 1382 } 1383 return false; 1384 } 1385 1386 /** 1387 * Writes a line to the build report file 1388 * 1389 * @param file build report file 1390 * @param level print level 1391 * @param string string to write 1392 */ 1393 public static void addLine(PrintWriter file, String level, String string) { 1394 log.debug("addLine: {}", string); 1395 if (file != null) { 1396 String[] lines = string.split(NEW_LINE); 1397 for (String line : lines) { 1398 printLine(file, level, line); 1399 } 1400 } 1401 } 1402 1403 // only used by build report 1404 private static void printLine(PrintWriter file, String level, String string) { 1405 int lineLengthMax = getLineLength(Setup.PORTRAIT, Setup.MONOSPACED, Font.PLAIN, Setup.getBuildReportFontSize()); 1406 if (string.length() > lineLengthMax) { 1407 String[] words = string.split(SPACE); 1408 StringBuffer sb = new StringBuffer(); 1409 for (String word : words) { 1410 if (sb.length() + word.length() < lineLengthMax) { 1411 sb.append(word + SPACE); 1412 } else { 1413 file.println(level + BUILD_REPORT_CHAR + SPACE + sb.toString()); 1414 sb = new StringBuffer(word + SPACE); 1415 } 1416 } 1417 string = sb.toString(); 1418 } 1419 file.println(level + BUILD_REPORT_CHAR + SPACE + string); 1420 } 1421 1422 /** 1423 * Writes string to file. No line length wrap or protection. 1424 * 1425 * @param file The File to write to. 1426 * @param string The string to write. 1427 */ 1428 protected void addLine(PrintWriter file, String string) { 1429 log.debug("addLine: {}", string); 1430 if (file != null) { 1431 file.println(string); 1432 } 1433 } 1434 1435 /** 1436 * Writes a string to a file. 1437 * 1438 * @param file The File to write to. 1439 * @param string The string to write. 1440 */ 1441 protected void newLine(PrintWriter file, String string) { 1442 if (!string.isEmpty()) { 1443 addLine(file, string); 1444 } 1445 1446 // this code is no longer needed, now provided in HardcopyWriter 1447 // if (string.contains(TEXT_SIZE_START)) { 1448 // addLine(file, string); 1449 // } else { 1450 // String[] lines = string.split(NEW_LINE); 1451 // for (String line : lines) { 1452 // String[] words = line.split(SPACE); 1453 // StringBuffer sb = new StringBuffer(); 1454 // for (String word : words) { 1455 // if (checkStringLength(sb.toString() + word, isManifest)) { 1456 // sb.append(word + SPACE); 1457 // } else { 1458 // if (sb.length() > 0) { 1459 // sb.setLength(sb.length() - 1); // remove last space added to string 1460 // addLine(file, sb.toString()); 1461 // } 1462 // sb = new StringBuffer(word + SPACE); 1463 // } 1464 // } 1465 // if (sb.length() > 0) { 1466 // sb.setLength(sb.length() - 1); // remove last space added to string 1467 // } 1468 // addLine(file, sb.toString()); 1469 // } 1470 // } 1471 } 1472 1473 /** 1474 * Adds a blank line to the file. 1475 * 1476 * @param file The File to write to. 1477 */ 1478 protected void newLine(PrintWriter file) { 1479 file.println(BLANK_LINE); 1480 } 1481 1482 /** 1483 * Splits a string (example-number) as long as the second part of the string 1484 * is an integer or if the first character after the hyphen is a left 1485 * parenthesis "(". 1486 * 1487 * @param name The string to split if necessary. 1488 * @return First half of the string. 1489 */ 1490 public static String splitString(String name) { 1491 String[] splitname = name.split(HYPHEN); 1492 // is the hyphen followed by a number or left parenthesis? 1493 if (splitname.length > 1 && !splitname[1].startsWith("(")) { 1494 try { 1495 Integer.parseInt(splitname[1]); 1496 } catch (NumberFormatException e) { 1497 // no return full name 1498 return name.trim(); 1499 } 1500 } 1501 return splitname[0].trim(); 1502 } 1503 1504 /** 1505 * Splits a string if there's a hyphen followed by a left parenthesis "-(". 1506 * 1507 * @param name the string to split 1508 * @return First half of the string. 1509 */ 1510 public static String splitStringLeftParenthesis(String name) { 1511 String[] splitname = name.split(HYPHEN); 1512 if (splitname.length > 1 && splitname[1].startsWith("(")) { 1513 return splitname[0].trim(); 1514 } 1515 return name.trim(); 1516 } 1517 1518 // returns true if there's work at location 1519 protected boolean isThereWorkAtLocation(List<Car> carList, List<Engine> engList, RouteLocation rl) { 1520 if (carList != null) { 1521 for (Car car : carList) { 1522 if (car.getRouteLocation() == rl || car.getRouteDestination() == rl) { 1523 return true; 1524 } 1525 } 1526 } 1527 if (engList != null) { 1528 for (Engine eng : engList) { 1529 if (eng.getRouteLocation() == rl || eng.getRouteDestination() == rl) { 1530 return true; 1531 } 1532 } 1533 } 1534 return false; 1535 } 1536 1537 /** 1538 * returns true if the train has work at the location 1539 * 1540 * @param train The Train. 1541 * @param location The Location. 1542 * @return true if the train has work at the location 1543 */ 1544 public static boolean isThereWorkAtLocation(Train train, Location location) { 1545 if (isThereWorkAtLocation(train, location, InstanceManager.getDefault(CarManager.class).getList(train))) { 1546 return true; 1547 } 1548 if (isThereWorkAtLocation(train, location, InstanceManager.getDefault(EngineManager.class).getList(train))) { 1549 return true; 1550 } 1551 return false; 1552 } 1553 1554 private static boolean isThereWorkAtLocation(Train train, Location location, List<? extends RollingStock> list) { 1555 for (RollingStock rs : list) { 1556 if ((rs.getRouteLocation() != null && 1557 rs.getTrack() != null && 1558 rs.getRouteLocation().getSplitName() 1559 .equals(location.getSplitName())) || 1560 (rs.getRouteDestination() != null && 1561 rs.getRouteDestination().getSplitName().equals(location.getSplitName()))) { 1562 return true; 1563 } 1564 } 1565 return false; 1566 } 1567 1568 protected void addCarsLocationUnknown(PrintWriter file, boolean isManifest) { 1569 List<Car> cars = carManager.getCarsLocationUnknown(); 1570 if (cars.size() == 0) { 1571 return; // no cars to search for! 1572 } 1573 newLine(file); 1574 newLine(file, Setup.getMiaComment()); 1575 if (Setup.isPrintHeadersEnabled()) { 1576 printHorizontalLine1(file, isManifest); 1577 newLine(file, SPACE + getHeader(Setup.getMissingCarMessageFormat(), false, false, false)); 1578 printHorizontalLine2(file, isManifest); 1579 } 1580 for (Car car : cars) { 1581 addSearchForCar(file, car); 1582 } 1583 } 1584 1585 private void addSearchForCar(PrintWriter file, Car car) { 1586 StringBuffer buf = new StringBuffer(); 1587 String[] format = Setup.getMissingCarMessageFormat(); 1588 for (String attribute : format) { 1589 buf.append(getCarAttribute(car, attribute, false, false)); 1590 } 1591 newLine(file, buf.toString()); 1592 } 1593 1594 /* 1595 * Gets an engine's attribute String. Returns empty if there isn't an 1596 * attribute and not using the tabular feature. isPickup true when engine is 1597 * being picked up. 1598 */ 1599 private String getEngineAttribute(Engine engine, String attribute, boolean isPickup) { 1600 if (!attribute.equals(Setup.BLANK)) { 1601 String s = SPACE + getEngineAttrib(engine, attribute, isPickup); 1602 if (Setup.isTabEnabled() || !s.isBlank()) { 1603 return s; 1604 } 1605 } 1606 return ""; 1607 } 1608 1609 /* 1610 * Can not use String case statement since Setup.MODEL, etc, are not fixed 1611 * strings. 1612 */ 1613 private String getEngineAttrib(Engine engine, String attribute, boolean isPickup) { 1614 if (attribute.equals(Setup.MODEL)) { 1615 return padAndTruncateIfNeeded(splitStringLeftParenthesis(engine.getModel()), 1616 InstanceManager.getDefault(EngineModels.class).getMaxNameLength()); 1617 } else if (attribute.equals(Setup.HP)) { 1618 return padAndTruncateIfNeeded(engine.getHp(), 5) + 1619 (Setup.isPrintHeadersEnabled() ? "" : TrainManifestHeaderText.getStringHeader_Hp()); 1620 } else if (attribute.equals(Setup.CONSIST)) { 1621 return padAndTruncateIfNeeded(engine.getConsistName(), 1622 InstanceManager.getDefault(ConsistManager.class).getMaxNameLength()); 1623 } else if (attribute.equals(Setup.DCC_ADDRESS)) { 1624 return padAndTruncateIfNeeded(engine.getDccAddress(), 1625 TrainManifestHeaderText.getStringHeader_DCC_Address().length()); 1626 } else if (attribute.equals(Setup.COMMENT)) { 1627 return padAndTruncateIfNeeded(engine.getComment(), engineManager.getMaxCommentLength()); 1628 } 1629 return getRollingStockAttribute(engine, attribute, isPickup, false); 1630 } 1631 1632 /* 1633 * Gets a car's attribute String. Returns empty if there isn't an attribute 1634 * and not using the tabular feature. isPickup true when car is being picked 1635 * up. isLocal true when car is performing a local move. 1636 */ 1637 private String getCarAttribute(Car car, String attribute, boolean isPickup, boolean isLocal) { 1638 if (!attribute.equals(Setup.BLANK)) { 1639 String s = SPACE + getCarAttrib(car, attribute, isPickup, isLocal); 1640 if (Setup.isTabEnabled() || !s.isBlank()) { 1641 return s; 1642 } 1643 } 1644 return ""; 1645 } 1646 1647 private String getCarAttrib(Car car, String attribute, boolean isPickup, boolean isLocal) { 1648 if (attribute.equals(Setup.LOAD)) { 1649 return ((car.isCaboose() && !Setup.isPrintCabooseLoadEnabled()) || 1650 (car.isPassenger() && !Setup.isPrintPassengerLoadEnabled())) 1651 ? padAndTruncateIfNeeded("", 1652 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()) 1653 : padAndTruncateIfNeeded(car.getLoadName().split(HYPHEN)[0], 1654 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()); 1655 } else if (attribute.equals(Setup.LOAD_TYPE)) { 1656 return padAndTruncateIfNeeded(car.getLoadType(), 1657 TrainManifestHeaderText.getStringHeader_Load_Type().length()); 1658 } else if (attribute.equals(Setup.HAZARDOUS)) { 1659 return (car.isHazardous() ? Setup.getHazardousMsg() 1660 : padAndTruncateIfNeeded("", Setup.getHazardousMsg().length())); 1661 } else if (attribute.equals(Setup.DROP_COMMENT)) { 1662 return padAndTruncateIfNeeded(car.getDropComment(), 1663 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()); 1664 } else if (attribute.equals(Setup.PICKUP_COMMENT)) { 1665 return padAndTruncateIfNeeded(car.getPickupComment(), 1666 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()); 1667 } else if (attribute.equals(Setup.KERNEL)) { 1668 return padAndTruncateIfNeeded(splitString(car.getKernelName()), 1669 InstanceManager.getDefault(KernelManager.class).getMaxNameLength()); 1670 } else if (attribute.equals(Setup.KERNEL_SIZE)) { 1671 if (car.isLead()) { 1672 return padAndTruncateIfNeeded(Integer.toString(car.getKernel().getSize()), 2); 1673 } 1674 return SPACE + SPACE; // assumes that kernel size is 99 or less 1675 } else if (attribute.equals(Setup.RWE)) { 1676 if (!car.getReturnWhenEmptyDestinationName().equals(Car.NONE)) { 1677 // format RWE destination and track name 1678 String rweAndTrackName = car.getSplitReturnWhenEmptyDestinationName(); 1679 if (!car.getReturnWhenEmptyDestTrackName().equals(Car.NONE)) { 1680 rweAndTrackName = rweAndTrackName + "," + SPACE + car.getSplitReturnWhenEmptyDestinationTrackName(); 1681 } 1682 return Setup.isPrintHeadersEnabled() 1683 ? padAndTruncateIfNeeded(rweAndTrackName, locationManager.getMaxLocationAndTrackNameLength()) 1684 : padAndTruncateIfNeeded( 1685 TrainManifestHeaderText.getStringHeader_RWE() + SPACE + rweAndTrackName, 1686 locationManager.getMaxLocationAndTrackNameLength() + 1687 TrainManifestHeaderText.getStringHeader_RWE().length() + 1688 3); 1689 } 1690 return padAndTruncateIfNeeded("", locationManager.getMaxLocationAndTrackNameLength()); 1691 } else if (attribute.equals(Setup.FINAL_DEST)) { 1692 return Setup.isPrintHeadersEnabled() 1693 ? padAndTruncateIfNeeded(car.getSplitFinalDestinationName(), 1694 locationManager.getMaxLocationNameLength()) 1695 : padAndTruncateIfNeeded( 1696 TrainManifestText.getStringFinalDestination() + 1697 SPACE + 1698 car.getSplitFinalDestinationName(), 1699 locationManager.getMaxLocationNameLength() + 1700 TrainManifestText.getStringFinalDestination().length() + 1701 1); 1702 } else if (attribute.equals(Setup.FINAL_DEST_TRACK)) { 1703 // format final destination and track name 1704 String FDAndTrackName = car.getSplitFinalDestinationName(); 1705 if (!car.getFinalDestinationTrackName().equals(Car.NONE)) { 1706 FDAndTrackName = FDAndTrackName + "," + SPACE + car.getSplitFinalDestinationTrackName(); 1707 } 1708 return Setup.isPrintHeadersEnabled() 1709 ? padAndTruncateIfNeeded(FDAndTrackName, locationManager.getMaxLocationAndTrackNameLength() + 2) 1710 : padAndTruncateIfNeeded(TrainManifestText.getStringFinalDestination() + SPACE + FDAndTrackName, 1711 locationManager.getMaxLocationAndTrackNameLength() + 1712 TrainManifestText.getStringFinalDestination().length() + 1713 3); 1714 } else if (attribute.equals(Setup.DIVISION)) { 1715 return padAndTruncateIfNeeded(car.getDivisionName(), 1716 InstanceManager.getDefault(DivisionManager.class).getMaxDivisionNameLength()); 1717 } else if (attribute.equals(Setup.BLOCKING_ORDER)) { 1718 if (car.isPassenger()) { 1719 return padAndTruncateIfNeeded(Integer.toString(car.getBlocking()), 3); 1720 } 1721 return SPACE + SPACE + SPACE; // assumes that blocking order is +/- 99 1722 } else if (attribute.equals(Setup.COMMENT)) { 1723 return padAndTruncateIfNeeded(car.getComment(), carManager.getMaxCommentLength()); 1724 } 1725 return getRollingStockAttribute(car, attribute, isPickup, isLocal); 1726 } 1727 1728 private String getRollingStockAttribute(RollingStock rs, String attribute, boolean isPickup, boolean isLocal) { 1729 try { 1730 if (attribute.equals(Setup.ORDER)) { 1731 return padAndTruncateIfNeeded(rs.getOrder(), 5); 1732 } else if (attribute.equals(Setup.NUMBER)) { 1733 return padAndTruncateIfNeeded(splitString(rs.getNumber()), Control.max_len_string_print_road_number); 1734 } else if (attribute.equals(Setup.ROAD)) { 1735 String road = rs.getRoadName().split(HYPHEN)[0]; 1736 return padAndTruncateIfNeeded(road, InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1737 } else if (attribute.equals(Setup.TYPE)) { 1738 String type = rs.getTypeName().split(HYPHEN)[0]; 1739 return padAndTruncateIfNeeded(type, InstanceManager.getDefault(CarTypes.class).getMaxNameLength()); 1740 } else if (attribute.equals(Setup.LENGTH)) { 1741 return padAndTruncateIfNeeded(rs.getLength() + Setup.getLengthUnitAbv(), 1742 InstanceManager.getDefault(CarLengths.class).getMaxNameLength()); 1743 } else if (attribute.equals(Setup.WEIGHT)) { 1744 return padAndTruncateIfNeeded(Integer.toString(rs.getAdjustedWeightTons()), 1745 Control.max_len_string_weight_name) + 1746 (Setup.isPrintHeadersEnabled() ? "" : TrainManifestHeaderText.getStringHeader_Weight()); 1747 } else if (attribute.equals(Setup.COLOR)) { 1748 return padAndTruncateIfNeeded(rs.getColor(), 1749 InstanceManager.getDefault(CarColors.class).getMaxNameLength()); 1750 } else if (((attribute.equals(Setup.LOCATION)) && (isPickup || isLocal)) || 1751 (attribute.equals(Setup.TRACK) && isPickup)) { 1752 return Setup.isPrintHeadersEnabled() 1753 ? padAndTruncateIfNeeded(rs.getSplitTrackName(), 1754 locationManager.getMaxTrackNameLength()) 1755 : padAndTruncateIfNeeded( 1756 TrainManifestText.getStringFrom() + SPACE + rs.getSplitTrackName(), 1757 TrainManifestText.getStringFrom().length() + 1758 locationManager.getMaxTrackNameLength() + 1759 1); 1760 } else if (attribute.equals(Setup.LOCATION) && !isPickup && !isLocal) { 1761 return Setup.isPrintHeadersEnabled() 1762 ? padAndTruncateIfNeeded(rs.getSplitLocationName(), 1763 locationManager.getMaxLocationNameLength()) 1764 : padAndTruncateIfNeeded( 1765 TrainManifestText.getStringFrom() + SPACE + rs.getSplitLocationName(), 1766 locationManager.getMaxLocationNameLength() + 1767 TrainManifestText.getStringFrom().length() + 1768 1); 1769 } else if (attribute.equals(Setup.DESTINATION) && isPickup) { 1770 if (Setup.isPrintHeadersEnabled()) { 1771 return padAndTruncateIfNeeded(rs.getSplitDestinationName(), 1772 locationManager.getMaxLocationNameLength()); 1773 } 1774 if (Setup.isTabEnabled()) { 1775 return padAndTruncateIfNeeded( 1776 TrainManifestText.getStringDest() + SPACE + rs.getSplitDestinationName(), 1777 TrainManifestText.getStringDest().length() + 1778 locationManager.getMaxLocationNameLength() + 1779 1); 1780 } else { 1781 return TrainManifestText.getStringDestination() + 1782 SPACE + 1783 rs.getSplitDestinationName(); 1784 } 1785 } else if ((attribute.equals(Setup.DESTINATION) || attribute.equals(Setup.TRACK)) && !isPickup) { 1786 return Setup.isPrintHeadersEnabled() 1787 ? padAndTruncateIfNeeded(rs.getSplitDestinationTrackName(), 1788 locationManager.getMaxTrackNameLength()) 1789 : padAndTruncateIfNeeded( 1790 TrainManifestText.getStringTo() + 1791 SPACE + 1792 rs.getSplitDestinationTrackName(), 1793 locationManager.getMaxTrackNameLength() + 1794 TrainManifestText.getStringTo().length() + 1795 1); 1796 } else if (attribute.equals(Setup.DEST_TRACK)) { 1797 // format destination name and destination track name 1798 String destAndTrackName = 1799 rs.getSplitDestinationName() + "," + SPACE + rs.getSplitDestinationTrackName(); 1800 return Setup.isPrintHeadersEnabled() 1801 ? padAndTruncateIfNeeded(destAndTrackName, 1802 locationManager.getMaxLocationAndTrackNameLength() + 2) 1803 : padAndTruncateIfNeeded(TrainManifestText.getStringDest() + SPACE + destAndTrackName, 1804 locationManager.getMaxLocationAndTrackNameLength() + 1805 TrainManifestText.getStringDest().length() + 1806 3); 1807 } else if (attribute.equals(Setup.OWNER)) { 1808 return padAndTruncateIfNeeded(rs.getOwnerName(), 1809 InstanceManager.getDefault(CarOwners.class).getMaxNameLength()); 1810 } else if (attribute.equals(Setup.LAST_TRAIN)) { 1811 String lastTrainName = padAndTruncateIfNeeded(rs.getLastTrainName(), 1812 InstanceManager.getDefault(TrainManager.class).getMaxTrainNameLength()); 1813 return Setup.isPrintHeadersEnabled() ? lastTrainName 1814 : TrainManifestHeaderText.getStringHeader_Last_Train() + SPACE + lastTrainName; 1815 } else if (attribute.equals(Setup.LAST_MOVED)) { 1816 // date format: 05/19/2026 07:12:58 1817 return padAndTruncateIfNeeded(rs.getLastDate(), RollingStock.DATE_TIME_LENGTH); 1818 } else if (attribute.equals(Setup.LAST_LOCATION)) { 1819 return padAndTruncateIfNeeded(rs.getLastLocationName(), locationManager.getMaxLocationNameLength()); 1820 // the three utility attributes that don't get printed but need to 1821 // be tabbed out 1822 } else if (attribute.equals(Setup.NO_NUMBER)) { 1823 return padAndTruncateIfNeeded("", 1824 Control.max_len_string_print_road_number - (UTILITY_CAR_COUNT_FIELD_SIZE + 1)); 1825 } else if (attribute.equals(Setup.NO_ROAD)) { 1826 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1827 } else if (attribute.equals(Setup.NO_COLOR)) { 1828 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarColors.class).getMaxNameLength()); 1829 } // there are four truncated manifest attributes 1830 else if (attribute.equals(Setup.NO_DEST_TRACK)) { 1831 return Setup.isPrintHeadersEnabled() 1832 ? padAndTruncateIfNeeded("", locationManager.getMaxLocationAndTrackNameLength() + 1) 1833 : ""; 1834 } else if ((attribute.equals(Setup.NO_LOCATION) && !isPickup) || 1835 (attribute.equals(Setup.NO_DESTINATION) && isPickup)) { 1836 return Setup.isPrintHeadersEnabled() 1837 ? padAndTruncateIfNeeded("", locationManager.getMaxLocationNameLength()) 1838 : ""; 1839 } else if (attribute.equals(Setup.NO_TRACK) || 1840 attribute.equals(Setup.NO_LOCATION) || 1841 attribute.equals(Setup.NO_DESTINATION)) { 1842 return Setup.isPrintHeadersEnabled() 1843 ? padAndTruncateIfNeeded("", locationManager.getMaxTrackNameLength()) 1844 : ""; 1845 } else if (attribute.equals(Setup.TAB)) { 1846 return createTabIfNeeded(Setup.getTab1Length() - 1); 1847 } else if (attribute.equals(Setup.TAB2)) { 1848 return createTabIfNeeded(Setup.getTab2Length() - 1); 1849 } else if (attribute.equals(Setup.TAB3)) { 1850 return createTabIfNeeded(Setup.getTab3Length() - 1); 1851 } 1852 // something isn't right! 1853 return Bundle.getMessage("ErrorPrintOptions", attribute); 1854 1855 } catch (ArrayIndexOutOfBoundsException e) { 1856 if (attribute.equals(Setup.ROAD)) { 1857 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1858 } else if (attribute.equals(Setup.TYPE)) { 1859 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarTypes.class).getMaxNameLength()); 1860 } 1861 // something isn't right! 1862 return Bundle.getMessage("ErrorPrintOptions", attribute); 1863 } 1864 } 1865 1866 /** 1867 * Two column header format. Left side pick ups, right side set outs 1868 * 1869 * @param file Manifest or switch list File. 1870 * @param isManifest True if manifest, false if switch list. 1871 * @param isTwoColumnTrack True if two column with track name. 1872 */ 1873 public void printEngineHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1874 int lineLength = getLineLength(isManifest); 1875 printHorizontalLine(file, isManifest); 1876 if (Setup.isPrintHeadersEnabled()) { 1877 if (!Setup.getPickupEnginePrefix().isBlank() || !Setup.getDropEnginePrefix().isBlank()) { 1878 // center engine pick up and set out text 1879 String s = padAndTruncate(tabString(Setup.getPickupEnginePrefix().trim(), 1880 lineLength / 4 - Setup.getPickupEnginePrefix().length() / 2), lineLength / 2) + 1881 VERTICAL_LINE_CHAR + 1882 tabString(Setup.getDropEnginePrefix(), 1883 lineLength / 4 - Setup.getDropEnginePrefix().length() / 2); 1884 s = padAndTruncate(s, lineLength); 1885 addLine(file, s); 1886 printHorizontalLine1(file, isManifest); 1887 } 1888 1889 String s = padAndTruncate(getPickupEngineHeader(isTwoColumnTrack), lineLength / 2); 1890 s = padAndTruncate(s + VERTICAL_LINE_CHAR + getDropEngineHeader(isTwoColumnTrack), lineLength); 1891 addLine(file, s); 1892 printHorizontalLine2(file, isManifest); 1893 } 1894 } 1895 1896 public void printPickupEngineHeader(PrintWriter file, boolean isManifest) { 1897 int lineLength = getLineLength(isManifest); 1898 printHorizontalLine1(file, isManifest); 1899 String s = padAndTruncate( 1900 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getPickupEngineHeader(!IS_TWO_COLUMN_TRACK), 1901 lineLength); 1902 addLine(file, s); 1903 printHorizontalLine2(file, isManifest); 1904 } 1905 1906 public void printDropEngineHeader(PrintWriter file, boolean isManifest) { 1907 int lineLength = getLineLength(isManifest); 1908 printHorizontalLine1(file, isManifest); 1909 String s = padAndTruncate( 1910 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getDropEngineHeader(!IS_TWO_COLUMN_TRACK), 1911 lineLength); 1912 addLine(file, s); 1913 printHorizontalLine2(file, isManifest); 1914 } 1915 1916 /** 1917 * Prints the two column header for cars. Left side pick ups, right side set 1918 * outs. 1919 * 1920 * @param file Manifest or Switch List File 1921 * @param isManifest True if manifest, false if switch list. 1922 * @param isTwoColumnTrack True if two column format using track names. 1923 */ 1924 public void printCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1925 int lineLength = getLineLength(isManifest); 1926 printHorizontalLine(file, isManifest); 1927 if (Setup.isPrintHeadersEnabled()) { 1928 // center pick up and set out text 1929 String s = padAndTruncate( 1930 tabString(Setup.getPickupCarPrefix(), lineLength / 4 - Setup.getPickupCarPrefix().length() / 2), 1931 lineLength / 2) + 1932 VERTICAL_LINE_CHAR + 1933 tabString(Setup.getDropCarPrefix(), lineLength / 4 - Setup.getDropCarPrefix().length() / 2); 1934 s = padAndTruncate(s, lineLength); 1935 addLine(file, s); 1936 printHorizontalLine1(file, isManifest); 1937 1938 s = padAndTruncate(getPickupCarHeader(isManifest, isTwoColumnTrack), lineLength / 2); 1939 s = padAndTruncate(s + VERTICAL_LINE_CHAR + getDropCarHeader(isManifest, isTwoColumnTrack), lineLength); 1940 addLine(file, s); 1941 printHorizontalLine2(file, isManifest); 1942 } 1943 } 1944 1945 public void printPickupCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1946 if (Setup.isPrintHeadersEnabled()) { 1947 printHorizontalLine1(file, isManifest); 1948 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + 1949 getPickupCarHeader(isManifest, isTwoColumnTrack), getLineLength(isManifest)); 1950 addLine(file, s); 1951 printHorizontalLine2(file, isManifest); 1952 } 1953 } 1954 1955 public void printDropCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1956 if (!Setup.isPrintHeadersEnabled() || getDropCarHeader(isManifest, isTwoColumnTrack).isBlank()) { 1957 return; 1958 } 1959 printHorizontalLine1(file, isManifest); 1960 String s = padAndTruncate( 1961 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getDropCarHeader(isManifest, isTwoColumnTrack), 1962 getLineLength(isManifest)); 1963 addLine(file, s); 1964 printHorizontalLine2(file, isManifest); 1965 } 1966 1967 public void printLocalCarMoveHeader(PrintWriter file, boolean isManifest) { 1968 if (!Setup.isPrintHeadersEnabled()) { 1969 return; 1970 } 1971 printHorizontalLine1(file, isManifest); 1972 String s = padAndTruncate( 1973 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getLocalMoveHeader(isManifest), 1974 getLineLength(isManifest)); 1975 addLine(file, s); 1976 printHorizontalLine2(file, isManifest); 1977 } 1978 1979 public String getPickupEngineHeader(boolean isTwoColumnTrack) { 1980 if (!isTwoColumnTrack) { 1981 return getHeader(Setup.getPickupEngineMessageFormat(), PICKUP, !LOCAL, ENGINE); 1982 } else { 1983 return getHeader(Setup.getPickupEngineTwoColumnByTrackMessageFormat(), PICKUP, !LOCAL, ENGINE); 1984 } 1985 } 1986 1987 public String getDropEngineHeader(boolean isTwoColumnTrack) { 1988 if (!isTwoColumnTrack) { 1989 return getHeader(Setup.getDropEngineMessageFormat(), !PICKUP, !LOCAL, ENGINE); 1990 } else { 1991 return getHeader(Setup.getDropEngineTwoColumnByTrackMessageFormat(), !PICKUP, !LOCAL, ENGINE); 1992 } 1993 } 1994 1995 public String getPickupCarHeader(boolean isManifest, boolean isTwoColumnTrack) { 1996 if (isManifest && !isTwoColumnTrack) { 1997 return getHeader(Setup.getPickupManifestMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1998 } else if (!isManifest && !isTwoColumnTrack) { 1999 return getHeader(Setup.getPickupSwitchListMessageFormat(), PICKUP, !LOCAL, !ENGINE); 2000 } else if (isManifest && isTwoColumnTrack) { 2001 return getHeader(Setup.getPickupTwoColumnByTrackManifestMessageFormat(), PICKUP, !LOCAL, !ENGINE); 2002 } else { 2003 return getHeader(Setup.getPickupTwoColumnByTrackSwitchListMessageFormat(), PICKUP, !LOCAL, !ENGINE); 2004 } 2005 } 2006 2007 public String getDropCarHeader(boolean isManifest, boolean isTwoColumnTrack) { 2008 if (isManifest && !isTwoColumnTrack) { 2009 return getHeader(Setup.getDropManifestMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 2010 } else if (!isManifest && !isTwoColumnTrack) { 2011 return getHeader(Setup.getDropSwitchListMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 2012 } else if (isManifest && isTwoColumnTrack) { 2013 return getHeader(Setup.getDropTwoColumnByTrackManifestMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 2014 } else { 2015 return getHeader(Setup.getDropTwoColumnByTrackSwitchListMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 2016 } 2017 } 2018 2019 public String getLocalMoveHeader(boolean isManifest) { 2020 if (isManifest) { 2021 return getHeader(Setup.getLocalManifestMessageFormat(), !PICKUP, LOCAL, !ENGINE); 2022 } else { 2023 return getHeader(Setup.getLocalSwitchListMessageFormat(), !PICKUP, LOCAL, !ENGINE); 2024 } 2025 } 2026 2027 private String getHeader(String[] format, boolean isPickup, boolean isLocal, boolean isEngine) { 2028 StringBuffer buf = new StringBuffer(); 2029 for (String attribute : format) { 2030 if (attribute.equals(Setup.BLANK)) { 2031 continue; 2032 } 2033 if (attribute.equals(Setup.ORDER)) { 2034 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Order(), 2035 5) + SPACE); 2036 } else if (attribute.equals(Setup.ROAD)) { 2037 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Road(), 2038 InstanceManager.getDefault(CarRoads.class).getMaxNameLength()) + SPACE); 2039 } else if (attribute.equals(Setup.NUMBER) && !isEngine) { 2040 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Number(), 2041 Control.max_len_string_print_road_number) + SPACE); 2042 } else if (attribute.equals(Setup.NUMBER) && isEngine) { 2043 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_EngineNumber(), 2044 Control.max_len_string_print_road_number) + SPACE); 2045 } else if (attribute.equals(Setup.TYPE)) { 2046 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Type(), 2047 InstanceManager.getDefault(CarTypes.class).getMaxNameLength()) + SPACE); 2048 } else if (attribute.equals(Setup.MODEL)) { 2049 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Model(), 2050 InstanceManager.getDefault(EngineModels.class).getMaxNameLength()) + SPACE); 2051 } else if (attribute.equals(Setup.HP)) { 2052 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Hp(), 2053 5) + SPACE); 2054 } else if (attribute.equals(Setup.CONSIST)) { 2055 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Consist(), 2056 InstanceManager.getDefault(ConsistManager.class).getMaxNameLength()) + SPACE); 2057 } else if (attribute.equals(Setup.DCC_ADDRESS)) { 2058 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_DCC_Address(), 2059 TrainManifestHeaderText.getStringHeader_DCC_Address().length()) + SPACE); 2060 } else if (attribute.equals(Setup.KERNEL)) { 2061 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Kernel(), 2062 InstanceManager.getDefault(KernelManager.class).getMaxNameLength()) + SPACE); 2063 } else if (attribute.equals(Setup.KERNEL_SIZE)) { 2064 buf.append(" "); // assume kernel size is 99 or less 2065 } else if (attribute.equals(Setup.LOAD)) { 2066 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Load(), 2067 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()) + SPACE); 2068 } else if (attribute.equals(Setup.LOAD_TYPE)) { 2069 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Load_Type(), 2070 TrainManifestHeaderText.getStringHeader_Load_Type().length()) + SPACE); 2071 } else if (attribute.equals(Setup.COLOR)) { 2072 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Color(), 2073 InstanceManager.getDefault(CarColors.class).getMaxNameLength()) + SPACE); 2074 } else if (attribute.equals(Setup.OWNER)) { 2075 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Owner(), 2076 InstanceManager.getDefault(CarOwners.class).getMaxNameLength()) + SPACE); 2077 } else if (attribute.equals(Setup.LENGTH)) { 2078 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Length(), 2079 InstanceManager.getDefault(CarLengths.class).getMaxNameLength()) + SPACE); 2080 } else if (attribute.equals(Setup.WEIGHT)) { 2081 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Weight(), 2082 Control.max_len_string_weight_name) + SPACE); 2083 } else if (attribute.equals(Setup.TRACK)) { 2084 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Track(), 2085 locationManager.getMaxTrackNameLength()) + SPACE); 2086 } else if (attribute.equals(Setup.LOCATION) && (isPickup || isLocal)) { 2087 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Location(), 2088 locationManager.getMaxTrackNameLength()) + SPACE); 2089 } else if (attribute.equals(Setup.LOCATION) && !isPickup) { 2090 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Location(), 2091 locationManager.getMaxLocationNameLength()) + SPACE); 2092 } else if (attribute.equals(Setup.DESTINATION) && !isPickup) { 2093 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Destination(), 2094 locationManager.getMaxTrackNameLength()) + SPACE); 2095 } else if (attribute.equals(Setup.DESTINATION) && isPickup) { 2096 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Destination(), 2097 locationManager.getMaxLocationNameLength()) + SPACE); 2098 } else if (attribute.equals(Setup.DEST_TRACK)) { 2099 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Dest_Track(), 2100 locationManager.getMaxLocationAndTrackNameLength() + 2) + SPACE); 2101 } else if (attribute.equals(Setup.FINAL_DEST)) { 2102 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Final_Dest(), 2103 locationManager.getMaxLocationNameLength()) + SPACE); 2104 } else if (attribute.equals(Setup.FINAL_DEST_TRACK)) { 2105 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Final_Dest_Track(), 2106 locationManager.getMaxLocationAndTrackNameLength() + 2) + SPACE); 2107 } else if (attribute.equals(Setup.HAZARDOUS)) { 2108 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Hazardous(), 2109 Setup.getHazardousMsg().length()) + SPACE); 2110 } else if (attribute.equals(Setup.RWE)) { 2111 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_RWE(), 2112 locationManager.getMaxLocationAndTrackNameLength()) + SPACE); 2113 } else if (attribute.equals(Setup.COMMENT)) { 2114 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Comment(), 2115 isEngine ? engineManager.getMaxCommentLength() : carManager.getMaxCommentLength()) + SPACE); 2116 } else if (attribute.equals(Setup.DROP_COMMENT)) { 2117 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Drop_Comment(), 2118 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()) + SPACE); 2119 } else if (attribute.equals(Setup.PICKUP_COMMENT)) { 2120 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Pickup_Comment(), 2121 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()) + SPACE); 2122 } else if (attribute.equals(Setup.DIVISION)) { 2123 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Division(), 2124 InstanceManager.getDefault(DivisionManager.class).getMaxDivisionNameLength()) + SPACE); 2125 } else if (attribute.equals(Setup.BLOCKING_ORDER)) { 2126 buf.append(" "); // assume blocking order +/- 99 2127 } else if (attribute.equals(Setup.LAST_TRAIN)) { 2128 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Last_Train(), 2129 InstanceManager.getDefault(TrainManager.class).getMaxTrainNameLength()) + SPACE); 2130 } else if (attribute.equals(Setup.LAST_MOVED)) { 2131 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Last_Moved(), 2132 RollingStock.DATE_TIME_LENGTH) + SPACE); 2133 } else if (attribute.equals(Setup.LAST_LOCATION)) { 2134 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Last_Location(), 2135 InstanceManager.getDefault(LocationManager.class).getMaxLocationNameLength()) + SPACE); 2136 } else if (attribute.equals(Setup.TAB)) { 2137 buf.append(createTabIfNeeded(Setup.getTab1Length())); 2138 } else if (attribute.equals(Setup.TAB2)) { 2139 buf.append(createTabIfNeeded(Setup.getTab2Length())); 2140 } else if (attribute.equals(Setup.TAB3)) { 2141 buf.append(createTabIfNeeded(Setup.getTab3Length())); 2142 } else { 2143 buf.append(attribute + SPACE); 2144 } 2145 } 2146 return buf.toString().stripTrailing(); 2147 } 2148 2149 protected void printTrackNameHeader(PrintWriter file, String trackName, boolean isManifest) { 2150 printHorizontalLine2(file, isManifest); 2151 int lineLength = getLineLength(isManifest); 2152 String s = padAndTruncate(tabString(trackName.trim(), lineLength / 4 - trackName.trim().length() / 2), 2153 lineLength / 2) + 2154 VERTICAL_LINE_CHAR + 2155 tabString(trackName.trim(), lineLength / 4 - trackName.trim().length() / 2); 2156 s = padAndTruncate(s, lineLength); 2157 addLine(file, s); 2158 if (Setup.isPrintHeaderLine3Enabled()) { 2159 printHorizontalLine(file, isManifest); 2160 } 2161 } 2162 2163 public void printHorizontalLine1(PrintWriter file, boolean isManifest) { 2164 if (Setup.isPrintHeaderLine1Enabled()) { 2165 printHorizontalLine(file, isManifest); 2166 } 2167 } 2168 2169 public void printHorizontalLine2(PrintWriter file, boolean isManifest) { 2170 if (Setup.isPrintHeaderLine2Enabled()) { 2171 printHorizontalLine(file, isManifest); 2172 } 2173 } 2174 2175 public void printHorizontalLine3(PrintWriter file, boolean isManifest) { 2176 if (Setup.isPrintHeadersEnabled() && Setup.isPrintHeaderLine3Enabled() || 2177 !Setup.getManifestFormat().equals(Setup.STANDARD_FORMAT)) { 2178 printHorizontalLine(file, isManifest); 2179 } 2180 } 2181 2182 /** 2183 * Prints a line across the entire page. 2184 * 2185 * @param file The File to print to. 2186 * @param isManifest True if manifest, false if switch list. 2187 */ 2188 public void printHorizontalLine(PrintWriter file, boolean isManifest) { 2189 printHorizontalLine(file, 0, getLineLength(isManifest)); 2190 } 2191 2192 public void printHorizontalLine(PrintWriter file, int start, int end) { 2193 StringBuffer sb = new StringBuffer(); 2194 while (start-- > 0) { 2195 sb.append(SPACE); 2196 } 2197 while (end-- > 0) { 2198 sb.append(HORIZONTAL_LINE_CHAR); 2199 } 2200 addLine(file, sb.toString()); 2201 } 2202 2203 public static String getISO8601Date(boolean isModelYear) { 2204 Calendar calendar = Calendar.getInstance(); 2205 // use the JMRI Timebase (which may be a fast clock). 2206 calendar.setTime(jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime()); 2207 if (isModelYear && !Setup.getYearModeled().isEmpty()) { 2208 try { 2209 calendar.set(Calendar.YEAR, Integer.parseInt(Setup.getYearModeled().trim())); 2210 } catch (NumberFormatException e) { 2211 return Setup.getYearModeled(); 2212 } 2213 } 2214 return (new StdDateFormat()).format(calendar.getTime()); 2215 } 2216 2217 public static String getDate(Date date) { 2218 SimpleDateFormat format = new SimpleDateFormat("M/dd/yyyy HH:mm"); // NOI18N 2219 if (Setup.is12hrFormatEnabled()) { 2220 format = new SimpleDateFormat("M/dd/yyyy hh:mm a"); // NOI18N 2221 } 2222 return format.format(date); 2223 } 2224 2225 public static String getDate(boolean isModelYear) { 2226 Calendar calendar = Calendar.getInstance(); 2227 // use the JMRI Timebase (which may be a fast clock). 2228 calendar.setTime(jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime()); 2229 if (isModelYear && !Setup.getYearModeled().equals(Setup.NONE)) { 2230 try { 2231 calendar.set(Calendar.YEAR, Integer.parseInt(Setup.getYearModeled().trim())); 2232 } catch (NumberFormatException e) { 2233 return Setup.getYearModeled(); 2234 } 2235 } 2236 return TrainCommon.getDate(calendar.getTime()); 2237 } 2238 2239 public static Date convertStringToDate(String date) { 2240 if (!date.isBlank()) { 2241 // create a date object from the string. 2242 try { 2243 // try MM/dd/yyyy HH:mm:ss. 2244 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); // NOI18N 2245 return formatter.parse(date); 2246 } catch (java.text.ParseException pe1) { 2247 // try the old 12 hour format (no seconds). 2248 try { 2249 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy hh:mmaa"); // NOI18N 2250 return formatter.parse(date); 2251 } catch (java.text.ParseException pe2) { 2252 try { 2253 // try 24hour clock. 2254 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm"); // NOI18N 2255 return formatter.parse(date); 2256 } catch (java.text.ParseException pe3) { 2257 log.debug("Not able to parse date: {}", date); 2258 } 2259 } 2260 } 2261 } 2262 return null; // there was no date specified. 2263 } 2264 2265 /* 2266 * Converts String time DAYS:HH:MM and DAYS:HH:MM AM/PM to minutes from 2267 * midnight. Note that the string time could be blank, and in that case 2268 * returns 0 minutes. 2269 */ 2270 public static int convertStringTime(String time) { 2271 int minutes = 0; 2272 boolean hrFormat = false; 2273 String[] splitTimePM = time.split(SPACE); 2274 if (splitTimePM.length > 1) { 2275 hrFormat = true; 2276 if (splitTimePM[1].equals(Bundle.getMessage("PM"))) { 2277 minutes = 12 * 60; 2278 } 2279 } 2280 String[] splitTime = splitTimePM[0].split(":"); 2281 2282 if (splitTime.length > 2) { 2283 // days:hrs:minutes 2284 if (hrFormat && splitTime[1].equals("12")) { 2285 splitTime[1] = "00"; 2286 } 2287 minutes += 24 * 60 * Integer.parseInt(splitTime[0]); 2288 minutes += 60 * Integer.parseInt(splitTime[1]); 2289 minutes += Integer.parseInt(splitTime[2]); 2290 } else if (splitTime.length == 2) { 2291 // hrs:minutes 2292 if (hrFormat && splitTime[0].equals("12")) { 2293 splitTime[0] = "00"; 2294 } 2295 minutes += 60 * Integer.parseInt(splitTime[0]); 2296 minutes += Integer.parseInt(splitTime[1]); 2297 } 2298 log.debug("convert time {} to minutes {}", time, minutes); 2299 return minutes; 2300 } 2301 2302 public String convertMinutesTime(int minutes) { 2303 int days = minutes / (24 * 60); 2304 int h = (minutes - (days * 24 * 60)) / 60; 2305 String sHours = String.format("%02d", h); 2306 int m = minutes - days * 24 * 60 - h * 60; 2307 String sMinutes = String.format("%02d", m); 2308 return Integer.toString(days) + ":" + sHours + ":" + sMinutes; 2309 } 2310 2311 /** 2312 * Pads out a string by adding spaces to the end of the string, and will 2313 * remove characters from the end of the string if the string exceeds the 2314 * field size. 2315 * 2316 * @param s The string to pad. 2317 * @param fieldSize The maximum length of the string. 2318 * @return A String the specified length 2319 */ 2320 public static String padAndTruncateIfNeeded(String s, int fieldSize) { 2321 if (Setup.isTabEnabled()) { 2322 return padAndTruncate(s, fieldSize); 2323 } 2324 return s; 2325 } 2326 2327 public static String padAndTruncate(String s, int fieldSize) { 2328 s = padString(s, fieldSize); 2329 if (s.length() > fieldSize) { 2330 s = s.substring(0, fieldSize); 2331 } 2332 return s; 2333 } 2334 2335 /** 2336 * Adjusts string to be a certain number of characters by adding spaces to 2337 * the end of the string. 2338 * 2339 * @param s The string to pad 2340 * @param fieldSize The fixed length of the string. 2341 * @return A String the specified length 2342 */ 2343 public static String padString(String s, int fieldSize) { 2344 StringBuffer buf = new StringBuffer(s); 2345 while (buf.length() < fieldSize) { 2346 buf.append(SPACE); 2347 } 2348 return buf.toString(); 2349 } 2350 2351 /** 2352 * Creates a String of spaces to create a tab for text. Tabs must be 2353 * enabled. Setup.isTabEnabled() 2354 * 2355 * @param tabSize the length of tab 2356 * @return tab 2357 */ 2358 public static String createTabIfNeeded(int tabSize) { 2359 if (Setup.isTabEnabled()) { 2360 return tabString("", tabSize); 2361 } 2362 return ""; 2363 } 2364 2365 protected static String tabString(String s, int tabSize) { 2366 StringBuffer buf = new StringBuffer(); 2367 // TODO this doesn't consider the length of s string. 2368 while (buf.length() < tabSize) { 2369 buf.append(SPACE); 2370 } 2371 buf.append(s); 2372 return buf.toString(); 2373 } 2374 2375 /** 2376 * Returns the line length for manifest or switch list printout. 2377 * 2378 * @param isManifest True if manifest. 2379 * @return line length for manifest or switch list. 2380 */ 2381 public static int getLineLength(boolean isManifest) { 2382 return getLineLength(isManifest ? Setup.getManifestOrientation() : Setup.getSwitchListOrientation(), 2383 Setup.getFontName(), Font.PLAIN, Setup.getManifestFontSize()); 2384 } 2385 2386 public static int getManifestHeaderLineLength() { 2387 return getLineLength(Setup.getManifestOrientation(), "SansSerif", Font.ITALIC, Setup.getManifestFontSize()); 2388 } 2389 2390 private static int getLineLength(String orientation, String fontName, int fontStyle, int fontSize) { 2391 Integer charsPerLine = InstanceManager.getDefault(TrainManager.class).getHardcopyWriterLineLength(fontName, 2392 fontStyle, fontSize, getPageSize(orientation), orientation.equals(Setup.LANDSCAPE)); 2393 if (charsPerLine == null) { 2394 // first try using hardcopywriter to get number of characters per line 2395 charsPerLine = getCharsPerLineHardcopyWriter(orientation, fontName, fontStyle, fontSize); 2396 if (charsPerLine == null) { 2397 charsPerLine = getCharsPerLine(orientation, fontName, fontStyle, fontSize); 2398 } 2399 log.debug("Number of characters per line {}, fontName: {}, fontStyle {}, fontSize {}", charsPerLine, 2400 fontName, 2401 fontStyle, fontSize); 2402 InstanceManager.getDefault(TrainManager.class).setHardcopyWriterLineLength(fontName, 2403 fontStyle, fontSize, getPageSize(orientation), orientation.equals(Setup.LANDSCAPE), charsPerLine); 2404 } 2405 return charsPerLine; 2406 } 2407 2408 private static Integer getCharsPerLineHardcopyWriter(String orientation, String fontName, int fontStyle, 2409 int fontSize) { 2410 // get hand held or half page dimensions in DPI 2411 Dimension pageSize = getFullPageSizeDPI(orientation); 2412 2413 double leftmargin = .5 * 72; 2414 double rightmargin = .5 * 72; 2415 double topmargin = .5 * 72; 2416 double bottommargin = .5 * 72; 2417 2418 if (orientation.equals(Setup.RECEIPT)) { 2419 leftmargin = .2 * 72; 2420 rightmargin = .2 * 72; 2421 } 2422 2423 Integer charsPerLine = null; 2424 try (HardcopyWriter writer = 2425 new HardcopyWriter(fontName, fontStyle, fontSize, leftmargin, rightmargin, topmargin, 2426 bottommargin, orientation.equals(Setup.LANDSCAPE), 2427 pageSize)) { 2428 2429 charsPerLine = writer.getCharactersPerLine(); 2430 2431 } catch (HardcopyWriter.PrintCanceledException ex) { 2432 log.debug("Print canceled"); 2433 } 2434 log.debug("orientation: {}, fontName: {}, fontStyle: {}, fontSize {}, chars/line: {}", orientation, fontName, 2435 fontStyle, fontSize, charsPerLine); 2436 return charsPerLine; 2437 } 2438 2439 /** 2440 * Returns null if standard paper size, otherwise paper dimensions for hand 2441 * held or half page in DPI. 2442 * 2443 * @param orientation paper size 2444 * @return null if landscape or portrait 2445 */ 2446 public static Dimension getFullPageSizeDPI(String orientation) { 2447 Dimension pageSize = null; 2448 if (orientation.equals(Setup.HANDHELD) || orientation.equals(Setup.HALFPAGE)) { 2449 // add margins to page size 2450 pageSize = new Dimension((getPageSize(orientation).width + PAPER_MARGINS.width), 2451 (getPageSize(orientation).height + PAPER_MARGINS.height)); 2452 } 2453 // 2.25 inch paper width 2454 if (orientation.equals(Setup.RECEIPT)) { 2455 // margins .1 inch width 2456 pageSize = new Dimension((getPageSize(orientation).width + RECEIPT_MARGINS.width), 2457 (getPageSize(orientation).height + RECEIPT_MARGINS.height)); 2458 } 2459 return pageSize; 2460 } 2461 2462 // backup method for determining characters per line 2463 private static int getCharsPerLine(String orientation, String fontName, int fontStyle, int fontSize) { 2464 Font font = new Font(fontName, fontStyle, fontSize); // NOI18N 2465 JLabel label = new JLabel(); 2466 FontMetrics metrics = label.getFontMetrics(font); 2467 int charwidth = metrics.charWidth('m'); 2468 if (charwidth == 0) { 2469 log.error("Line length charater width equal to zero. font size: {}, fontName: {}", fontSize, fontName); 2470 charwidth = fontSize / 2; // create a reasonable character width 2471 } 2472 // compute lines and columns within margins 2473 int charsPerLine = getPageSize(orientation).width / charwidth; 2474 return charsPerLine; 2475 } 2476 2477 /** 2478 * Checks to see if the string fits on the page. 2479 * 2480 * @return false if string length is longer than page width. 2481 */ 2482 private boolean checkStringLength(String string, boolean isManifest) { 2483 // ignore color and bold controls when determining line length 2484 string = getOnlyText(string); 2485 return string.length() <= getLineLength(isManifest); 2486 } 2487 2488 protected static final Dimension PAPER_MARGINS = new Dimension(84, 72); 2489 protected static final Dimension RECEIPT_MARGINS = new Dimension(28, 72); 2490 2491 public static Dimension getPageSize(String orientation) { 2492 // page size has been adjusted to account for margins of .5 2493 // Dimension(72, 72) left & right, top & bottom margins 2494 Dimension pagesize = new Dimension(523, 720); // Portrait 8.5 x 11 2495 if (orientation.equals(Setup.LANDSCAPE)) { 2496 pagesize = new Dimension(720, 523); // 11 x 8.5 2497 } 2498 if (orientation.equals(Setup.HALFPAGE)) { 2499 pagesize = new Dimension(261, 720); // 4.25 x 11 2500 } 2501 if (orientation.equals(Setup.HANDHELD)) { 2502 pagesize = new Dimension(206, 720); // 3.25 x 11 2503 } 2504 if (orientation.equals(Setup.RECEIPT)) { 2505 // page size has been adjusted to account for margins of .2 2506 pagesize = new Dimension(136, 720); // 2.25 x 11 (58mm) 2507 } 2508 return pagesize; 2509 } 2510 2511 /** 2512 * Produces a string using commas and spaces between the strings provided in 2513 * the array. Does not check for embedded commas in the string array. 2514 * 2515 * @param array The string array to be formated. 2516 * @return formated string using commas and spaces 2517 */ 2518 public static String formatStringToCommaSeparated(String[] array) { 2519 StringBuffer sbuf = new StringBuffer(""); 2520 for (String s : array) { 2521 if (s != null) { 2522 sbuf = sbuf.append(s + "," + SPACE); 2523 } 2524 } 2525 if (sbuf.length() > 2) { 2526 sbuf.setLength(sbuf.length() - 2); // remove trailing separators 2527 } 2528 return sbuf.toString(); 2529 } 2530 2531 private void addLine(PrintWriter file, StringBuffer buf, Color color) { 2532 String s = buf.toString(); 2533 if (!s.isBlank()) { 2534 addLine(file, formatColorString(s, color)); 2535 } 2536 } 2537 2538 /** 2539 * Adds HTML like color text control characters around a string. Note that 2540 * black is the standard text color, and if black is requested no control 2541 * characters are added. 2542 * 2543 * @param text the text to be modified 2544 * @param color the color the text is to be printed 2545 * @return formated text with color modifiers 2546 */ 2547 public static String formatColorString(String text, Color color) { 2548 return formatColorString(text, color, false); 2549 } 2550 2551 public static String formatColorString(String text, Color color, boolean isBold) { 2552 String s = text; 2553 if (!color.equals(Color.black)) { 2554 s = TEXT_COLOR_START + ColorUtil.colorToColorName(color) + TEXT_COLOR_DONE + text + TEXT_COLOR_END; 2555 } 2556 if (isBold) { 2557 s = TEXT_BOLD + s + TEXT_BOLD_END; 2558 } 2559 return s; 2560 } 2561 2562 public static String getOnlyText(String string) { 2563 string = getTextSizeString(string); 2564 string = getTextColorString(string); 2565 string = getTextBoldString(string); 2566 string = getTextItalicString(string); 2567 return string; 2568 } 2569 2570 public static String getTextColorString(String string) { 2571 String text = string; 2572 while (text.contains(TEXT_COLOR_START) || text.contains(TEXT_COLOR_END)) { 2573 text = stripColorControlCharacters(text); 2574 } 2575 return text; 2576 } 2577 2578 /** 2579 * Removes the color text control characters around the desired string 2580 * 2581 * @param string the string with control characters 2582 * @return pure text 2583 */ 2584 private static String stripColorControlCharacters(String string) { 2585 String text = string; 2586 if (string.contains(TEXT_COLOR_START)) { 2587 text = string.substring(0, string.indexOf(TEXT_COLOR_START)) + 2588 string.substring(string.indexOf(TEXT_COLOR_DONE, string.indexOf(TEXT_COLOR_START)) + 2589 TEXT_COLOR_DONE.length()); 2590 } 2591 if (text.contains(TEXT_COLOR_END)) { 2592 text = text.substring(0, text.indexOf(TEXT_COLOR_END)) + 2593 string.substring(string.indexOf(TEXT_COLOR_END) + TEXT_COLOR_END.length()); 2594 } 2595 return text; 2596 } 2597 2598 public static Color getTextColor(String string) { 2599 Color color = Color.black; 2600 if (string.contains(TEXT_COLOR_START)) { 2601 try { 2602 String c = string.substring(string.indexOf(TEXT_COLOR_START) + TEXT_COLOR_START.length(), 2603 string.indexOf(TEXT_COLOR_DONE, string.indexOf(TEXT_COLOR_START))); 2604 2605 color = ColorUtil.stringToColor(c); 2606 } catch ( 2607 IllegalArgumentException | 2608 StringIndexOutOfBoundsException e) { 2609 log.error("Exception when getting text color: {} {}", string, e.getLocalizedMessage()); 2610 } 2611 } 2612 return color; 2613 } 2614 2615 public static String getTextColorName(String string) { 2616 return ColorUtil.colorToColorName(getTextColor(string)); 2617 } 2618 2619 public static String getTextBoldString(String string) { 2620 String text = string; 2621 while (text.contains(TEXT_BOLD) || text.contains(TEXT_BOLD_END)) { 2622 text = stripStyleControlCharacters(text, TEXT_BOLD, TEXT_BOLD_END); 2623 } 2624 return text; 2625 } 2626 2627 public static boolean isTextBold(String string) { 2628 return (!isTextUserModified(string) && string.contains(TEXT_BOLD)); 2629 } 2630 2631 public static String getTextItalicString(String string) { 2632 String text = string; 2633 while (text.contains(TEXT_ITALIC) || text.contains(TEXT_ITALIC_END)) { 2634 text = stripStyleControlCharacters(text, TEXT_ITALIC, TEXT_ITALIC_END); 2635 } 2636 return text; 2637 } 2638 2639 private static String stripStyleControlCharacters(String text, String startStyle, String endStyle) { 2640 if (text.contains(startStyle)) { 2641 text = text.substring(0, text.indexOf(startStyle)) + 2642 text.substring(text.indexOf(startStyle) + startStyle.length()); 2643 } 2644 if (text.contains(endStyle)) { 2645 text = text.substring(0, text.indexOf(endStyle)) + 2646 text.substring(text.indexOf(endStyle) + endStyle.length()); 2647 } 2648 return text; 2649 } 2650 2651 public static int getFontSize(String string) { 2652 int fontSize = 0; 2653 if (string.contains(TEXT_SIZE_START)) { 2654 String size = string.substring(string.indexOf(TEXT_SIZE_START) + TEXT_SIZE_START.length(), 2655 string.indexOf(TEXT_SIZE_DONE, string.indexOf(TEXT_SIZE_START))); 2656 try { 2657 fontSize = Integer.parseInt(size); 2658 } catch (NumberFormatException e) { 2659 log.error("Font size not an integer: {} Text: {} {}", size, string, e.getLocalizedMessage()); 2660 fontSize = Setup.getManifestFontSize(); 2661 } 2662 } 2663 return fontSize; 2664 } 2665 2666 public static String getTextSizeString(String string) { 2667 String text = string; 2668 while (text.contains(TEXT_SIZE_START) || text.contains(TEXT_SIZE_END)) { 2669 text = stripSizeControlCharacters(text); 2670 } 2671 return text; 2672 } 2673 2674 private static String stripSizeControlCharacters(String string) { 2675 String text = string; 2676 if (string.contains(TEXT_SIZE_START)) { 2677 text = string.substring(0, string.indexOf(TEXT_SIZE_START)) + 2678 string.substring( 2679 string.indexOf(TEXT_SIZE_DONE, string.indexOf(TEXT_SIZE_START)) + TEXT_SIZE_DONE.length()); 2680 } 2681 if (text.contains(TEXT_SIZE_END)) { 2682 text = text.substring(0, text.indexOf(TEXT_SIZE_END)) + 2683 string.substring(string.indexOf(TEXT_SIZE_END) + TEXT_SIZE_END.length()); 2684 } 2685 return text; 2686 } 2687 2688 /** 2689 * Used to determine if user has modified the text with bold and color 2690 * controls. If there are two or more bold or color controls, then user 2691 * modified text. If there is only one bold and/or color control they have 2692 * to be at the start and end of the text string, otherwise modified by 2693 * user. 2694 * 2695 * @param string the string to test 2696 * @return true if modified by user 2697 */ 2698 public static boolean isTextUserModified(String string) { 2699 String text = string; 2700 // current GUI doesn't provide user font size change or italic characters 2701 if (text.contains(TEXT_SIZE_START) || 2702 text.contains(TEXT_SIZE_END) || 2703 text.contains(TEXT_ITALIC) || 2704 text.contains(TEXT_ITALIC_END)) { 2705 return true; 2706 } 2707 int count = 0; 2708 while (text.contains(TEXT_BOLD) || text.contains(TEXT_BOLD_END)) { 2709 text = stripStyleControlCharacters(text, TEXT_BOLD, TEXT_BOLD_END); 2710 count++; 2711 } 2712 if (count > 1) { 2713 return true; // two or more bold controls 2714 } 2715 if (count == 1) { 2716 // if bold at start and end bold at end then program added 2717 String[] test = string.split(TEXT_BOLD); 2718 if (!test[0].isBlank()) { 2719 return true; 2720 } 2721 test = string.split(TEXT_BOLD_END); 2722 if (test.length > 1 && !test[1].isBlank()) { 2723 return true; 2724 } 2725 } 2726 count = 0; 2727 String textColor = string; 2728 while (textColor.contains(TEXT_COLOR_START) || textColor.contains(TEXT_COLOR_END)) { 2729 textColor = stripColorControlCharacters(textColor); 2730 count++; 2731 } 2732 if (count == 1) { 2733 // if color controls at start and end then program added 2734 String[] test = text.split(TEXT_COLOR_START); 2735 if (!test[0].isBlank()) { 2736 return true; 2737 } 2738 test = text.split(TEXT_COLOR_END); 2739 if (test.length > 1 && !test[1].isBlank()) { 2740 return true; 2741 } 2742 } 2743 return count > 1; 2744 } 2745 2746 private static final Logger log = LoggerFactory.getLogger(TrainCommon.class); 2747}