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