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