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