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 setPickupAndSetoutTimes(Train train, RouteLocation rl, List<RollingStock> list) { 615 String expectedDepartureTime = train.getExpectedDepartureTime(rl, true); 616 for (RollingStock rs : list) { 617 if (rs.getRouteLocation() == rl) { 618 rs.setPickupTime(expectedDepartureTime); 619 } 620 if (rs.getRouteDestination() == rl) { 621 rs.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.getDepartureTimeHourMinutes().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, train.getExpectedDepartureTime(rl), 654 train.getSplitName(), train.getDescription(), 655 rl.getLocation().getDivisionName()}); 656 } else if (Setup.isUseDepartureTimeEnabled() && 657 rl != train.getTrainTerminatesRouteLocation() && 658 !expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 659 // Scheduled work at {0}, departure time {1} 660 msg = MessageFormat.format(messageFormatText = TrainManifestText 661 .getStringWorkDepartureTime(), 662 new Object[]{routeLocationName, 663 train.getExpectedDepartureTime(rl), train.getSplitName(), 664 train.getDescription(), rl.getLocation().getDivisionName()}); 665 } else if (!expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 666 // Scheduled work at {0}, arrival time {1} 667 msg = MessageFormat.format(messageFormatText = TrainManifestText 668 .getStringWorkArrivalTime(), 669 new Object[]{routeLocationName, expectedArrivalTime, 670 train.getSplitName(), train.getDescription(), 671 rl.getLocation().getDivisionName()}); 672 } 673 } 674 return msg; 675 } catch (IllegalArgumentException e) { 676 msg = Bundle.getMessage("ErrorIllegalArgument", 677 Bundle.getMessage("TitleManifestText"), e.getLocalizedMessage()) + NEW_LINE + messageFormatText; 678 log.error(msg); 679 log.error("Illegal argument", e); 680 return msg; 681 } 682 } 683 684 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "SLF4J_FORMAT_SHOULD_BE_CONST", 685 justification = "Only when exception") 686 public static String getSwitchListTrainStatus(Train train, RouteLocation rl) { 687 String expectedArrivalTime = train.getExpectedArrivalTime(rl); 688 String msg = ""; 689 String messageFormatText = ""; // the text being formated in case there's an exception 690 try { 691 if (train.isLocalSwitcher()) { 692 // Use Manifest text for local departure 693 // Scheduled work at {0}, departure time {1} 694 msg = MessageFormat.format(messageFormatText = TrainManifestText.getStringWorkDepartureTime(), 695 new Object[]{splitString(train.getTrainDepartsName()), train.getFormatedDepartureTime(), 696 train.getSplitName(), train.getDescription(), 697 rl.getLocation().getDivisionName()}); 698 } else if (rl == train.getTrainDepartsRouteLocation()) { 699 // Departs {0} {1}bound at {2} 700 msg = MessageFormat.format(messageFormatText = TrainSwitchListText.getStringDepartsAt(), 701 new Object[]{splitString(train.getTrainDepartsName()), rl.getTrainDirectionString(), 702 train.getFormatedDepartureTime()}); 703 } else if (Setup.isUseSwitchListDepartureTimeEnabled() && 704 rl != train.getTrainTerminatesRouteLocation() && 705 !train.isTrainEnRoute()) { 706 // Departs {0} at {1} expected arrival {2}, arrives {3}bound 707 msg = MessageFormat.format( 708 messageFormatText = TrainSwitchListText.getStringDepartsAtExpectedArrival(), 709 new Object[]{splitString(rl.getName()), 710 train.getExpectedDepartureTime(rl), expectedArrivalTime, 711 rl.getTrainDirectionString()}); 712 } else if (Setup.isUseSwitchListDepartureTimeEnabled() && 713 rl == train.getCurrentRouteLocation() && 714 rl != train.getTrainTerminatesRouteLocation() && 715 !rl.getDepartureTimeHourMinutes().equals(RouteLocation.NONE)) { 716 // Departs {0} {1}bound at {2} 717 msg = MessageFormat.format(messageFormatText = TrainSwitchListText.getStringDepartsAt(), 718 new Object[]{splitString(rl.getName()), rl.getTrainDirectionString(), 719 rl.getFormatedDepartureTime()}); 720 } else if (train.isTrainEnRoute()) { 721 if (!expectedArrivalTime.equals(Train.ALREADY_SERVICED)) { 722 // Departed {0}, expect to arrive in {1}, arrives {2}bound 723 msg = MessageFormat.format(messageFormatText = TrainSwitchListText.getStringDepartedExpected(), 724 new Object[]{splitString(train.getTrainDepartsName()), expectedArrivalTime, 725 rl.getTrainDirectionString(), train.getCurrentLocationName()}); 726 } 727 } else { 728 // Departs {0} at {1} expected arrival {2}, arrives {3}bound 729 msg = MessageFormat.format( 730 messageFormatText = TrainSwitchListText.getStringDepartsAtExpectedArrival(), 731 new Object[]{splitString(train.getTrainDepartsName()), 732 train.getFormatedDepartureTime(), expectedArrivalTime, 733 rl.getTrainDirectionString()}); 734 } 735 return msg; 736 } catch (IllegalArgumentException e) { 737 msg = Bundle.getMessage("ErrorIllegalArgument", 738 Bundle.getMessage("TitleSwitchListText"), e.getLocalizedMessage()) + NEW_LINE + messageFormatText; 739 log.error(msg); 740 log.error("Illegal argument", e); 741 return msg; 742 } 743 } 744 745 int index = 0; 746 747 /* 748 * Used by two column format. Local moves (pulls and spots) are lined up 749 * when using this format, 750 */ 751 private String appendSetoutString(String s, List<Car> carList, RouteLocation rl, boolean local, boolean isManifest, 752 boolean isTwoColumnTrack) { 753 while (index < carList.size()) { 754 Car car = carList.get(index++); 755 if (local && car.isLocalMove()) { 756 continue; // skip local moves 757 } 758 // car list is already sorted by destination track 759 if (car.getRouteDestination() == rl) { 760 String so = appendSetoutString(s, carList, rl, car, isManifest, isTwoColumnTrack); 761 // check for utility car 762 if (!so.equals(s)) { 763 return so; 764 } 765 } 766 } 767 // no set out for this line 768 return s + VERTICAL_LINE_CHAR + padAndTruncate("", getLineLength(isManifest) / 2 - 1); 769 } 770 771 /* 772 * Used by two column, track names shown in the columns. 773 */ 774 private String appendSetoutString(String s, String trackName, List<Car> carList, RouteLocation rl, 775 boolean isManifest, boolean isTwoColumnTrack) { 776 for (Car car : carList) { 777 if (!doneCars.contains(car) && 778 car.getRouteDestination() == rl && 779 trackName.equals(car.getSplitDestinationTrackName())) { 780 doneCars.add(car); 781 String so = appendSetoutString(s, carList, rl, car, isManifest, isTwoColumnTrack); 782 // check for utility car 783 if (!so.equals(s)) { 784 return so; 785 } 786 } 787 } 788 // no set out for this track 789 return s + VERTICAL_LINE_CHAR + padAndTruncate("", getLineLength(isManifest) / 2 - 1); 790 } 791 792 /* 793 * Appends to string the vertical line character, and the car set out 794 * string. Used in two column format. 795 */ 796 private String appendSetoutString(String s, List<Car> carList, RouteLocation rl, Car car, boolean isManifest, 797 boolean isTwoColumnTrack) { 798 _dropCars = true; 799 String dropText; 800 801 if (car.isUtility()) { 802 dropText = setoutUtilityCars(carList, car, !LOCAL, isManifest, isTwoColumnTrack); 803 if (dropText == null) { 804 return s; // no changes to the input string 805 } 806 } else { 807 dropText = dropCar(car, isManifest, isTwoColumnTrack).trim(); 808 } 809 810 dropText = padAndTruncate(dropText.trim(), getLineLength(isManifest) / 2 - 1); 811 dropText = formatColorString(dropText, car.isLocalMove() ? Setup.getLocalColor() : Setup.getDropColor()); 812 return s + VERTICAL_LINE_CHAR + dropText; 813 } 814 815 /** 816 * Adds the car's pick up string to the output file using the truncated 817 * manifest format 818 * 819 * @param file Manifest or switch list File 820 * @param car The car being printed. 821 * @param isManifest True if manifest, false if switch list. 822 */ 823 protected void pickUpCarTruncated(PrintWriter file, Car car, boolean isManifest) { 824 pickUpCar(file, car, 825 new StringBuffer(padAndTruncateIfNeeded(Setup.getPickupCarPrefix(), Setup.getManifestPrefixLength())), 826 Setup.getPickupTruncatedManifestMessageFormat(), isManifest); 827 } 828 829 /** 830 * Adds the car's pick up string to the output file using the manifest or 831 * switch list format 832 * 833 * @param file Manifest or switch list File 834 * @param car The car being printed. 835 * @param isManifest True if manifest, false if switch list. 836 */ 837 protected void pickUpCar(PrintWriter file, Car car, boolean isManifest) { 838 if (isManifest) { 839 pickUpCar(file, car, 840 new StringBuffer( 841 padAndTruncateIfNeeded(Setup.getPickupCarPrefix(), Setup.getManifestPrefixLength())), 842 Setup.getPickupManifestMessageFormat(), isManifest); 843 } else { 844 pickUpCar(file, car, new StringBuffer( 845 padAndTruncateIfNeeded(Setup.getSwitchListPickupCarPrefix(), Setup.getSwitchListPrefixLength())), 846 Setup.getPickupSwitchListMessageFormat(), isManifest); 847 } 848 } 849 850 private void pickUpCar(PrintWriter file, Car car, StringBuffer buf, String[] format, boolean isManifest) { 851 if (car.isLocalMove()) { 852 return; // print nothing local move, see dropCar 853 } 854 for (String attribute : format) { 855 String s = getCarAttribute(car, attribute, PICKUP, !LOCAL); 856 if (!checkStringLength(buf.toString() + s, isManifest)) { 857 addLine(file, buf, Setup.getPickupColor()); 858 buf = new StringBuffer(TAB); // new line 859 } 860 buf.append(s); 861 } 862 addLine(file, buf, Setup.getPickupColor()); 863 } 864 865 /** 866 * Returns the pick up car string. Useful for frames like train conductor 867 * and yardmaster. 868 * 869 * @param car The car being printed. 870 * @param isManifest when true use manifest format, when false use 871 * switch list format 872 * @param isTwoColumnTrack True if printing using two column format sorted 873 * by track name. 874 * @return pick up car string 875 */ 876 public String pickupCar(Car car, boolean isManifest, boolean isTwoColumnTrack) { 877 StringBuffer buf = new StringBuffer(); 878 String[] format; 879 if (isManifest && !isTwoColumnTrack) { 880 format = Setup.getPickupManifestMessageFormat(); 881 } else if (!isManifest && !isTwoColumnTrack) { 882 format = Setup.getPickupSwitchListMessageFormat(); 883 } else if (isManifest && isTwoColumnTrack) { 884 format = Setup.getPickupTwoColumnByTrackManifestMessageFormat(); 885 } else { 886 format = Setup.getPickupTwoColumnByTrackSwitchListMessageFormat(); 887 } 888 for (String attribute : format) { 889 buf.append(getCarAttribute(car, attribute, PICKUP, !LOCAL)); 890 } 891 return buf.toString(); 892 } 893 894 /** 895 * Adds the car's set out string to the output file using the truncated 896 * manifest format. Does not print out local moves. Local moves are only 897 * shown on the switch list for that location. 898 * 899 * @param file Manifest or switch list File 900 * @param car The car being printed. 901 * @param isManifest True if manifest, false if switch list. 902 */ 903 protected void truncatedDropCar(PrintWriter file, Car car, boolean isManifest) { 904 // local move? 905 if (car.isLocalMove()) { 906 return; // yes, don't print local moves on train manifest 907 } 908 dropCar(file, car, new StringBuffer(Setup.getDropCarPrefix()), Setup.getDropTruncatedManifestMessageFormat(), 909 false, isManifest); 910 } 911 912 /** 913 * Adds the car's set out string to the output file using the manifest or 914 * switch list format 915 * 916 * @param file Manifest or switch list File 917 * @param car The car being printed. 918 * @param isManifest True if manifest, false if switch list. 919 */ 920 protected void dropCar(PrintWriter file, Car car, boolean isManifest) { 921 boolean isLocal = car.isLocalMove(); 922 if (isManifest) { 923 StringBuffer buf = new StringBuffer( 924 padAndTruncateIfNeeded(Setup.getDropCarPrefix(), Setup.getManifestPrefixLength())); 925 String[] format = Setup.getDropManifestMessageFormat(); 926 if (isLocal) { 927 buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getLocalPrefix(), Setup.getManifestPrefixLength())); 928 format = Setup.getLocalManifestMessageFormat(); 929 } 930 dropCar(file, car, buf, format, isLocal, isManifest); 931 } else { 932 StringBuffer buf = new StringBuffer( 933 padAndTruncateIfNeeded(Setup.getSwitchListDropCarPrefix(), Setup.getSwitchListPrefixLength())); 934 String[] format = Setup.getDropSwitchListMessageFormat(); 935 if (isLocal) { 936 buf = new StringBuffer( 937 padAndTruncateIfNeeded(Setup.getSwitchListLocalPrefix(), Setup.getSwitchListPrefixLength())); 938 format = Setup.getLocalSwitchListMessageFormat(); 939 } 940 dropCar(file, car, buf, format, isLocal, isManifest); 941 } 942 } 943 944 private void dropCar(PrintWriter file, Car car, StringBuffer buf, String[] format, boolean isLocal, 945 boolean isManifest) { 946 for (String attribute : format) { 947 String s = getCarAttribute(car, attribute, !PICKUP, isLocal); 948 if (!checkStringLength(buf.toString() + s, isManifest)) { 949 addLine(file, buf, isLocal ? Setup.getLocalColor() : Setup.getDropColor()); 950 buf = new StringBuffer(TAB); // new line 951 } 952 buf.append(s); 953 } 954 addLine(file, buf, isLocal ? Setup.getLocalColor() : Setup.getDropColor()); 955 } 956 957 /** 958 * Returns the drop car string. Useful for frames like train conductor and 959 * yardmaster. 960 * 961 * @param car The car being printed. 962 * @param isManifest when true use manifest format, when false use 963 * switch list format 964 * @param isTwoColumnTrack True if printing using two column format. 965 * @return drop car string 966 */ 967 public String dropCar(Car car, boolean isManifest, boolean isTwoColumnTrack) { 968 StringBuffer buf = new StringBuffer(); 969 String[] format; 970 if (isManifest && !isTwoColumnTrack) { 971 format = Setup.getDropManifestMessageFormat(); 972 } else if (!isManifest && !isTwoColumnTrack) { 973 format = Setup.getDropSwitchListMessageFormat(); 974 } else if (isManifest && isTwoColumnTrack) { 975 format = Setup.getDropTwoColumnByTrackManifestMessageFormat(); 976 } else { 977 format = Setup.getDropTwoColumnByTrackSwitchListMessageFormat(); 978 } 979 // TODO the Setup.Location doesn't work correctly for the conductor 980 // window due to the fact that the car can be in the train and not 981 // at its starting location. 982 // Therefore we use the local true to disable it. 983 boolean local = false; 984 if (car.getTrack() == null) { 985 local = true; 986 } 987 for (String attribute : format) { 988 buf.append(getCarAttribute(car, attribute, !PICKUP, local)); 989 } 990 return buf.toString(); 991 } 992 993 /** 994 * Returns the move car string. Useful for frames like train conductor and 995 * yardmaster. 996 * 997 * @param car The car being printed. 998 * @param isManifest when true use manifest format, when false use switch 999 * list format 1000 * @return move car string 1001 */ 1002 public String localMoveCar(Car car, boolean isManifest) { 1003 StringBuffer buf = new StringBuffer(); 1004 String[] format; 1005 if (isManifest) { 1006 format = Setup.getLocalManifestMessageFormat(); 1007 } else { 1008 format = Setup.getLocalSwitchListMessageFormat(); 1009 } 1010 for (String attribute : format) { 1011 buf.append(getCarAttribute(car, attribute, !PICKUP, LOCAL)); 1012 } 1013 return buf.toString(); 1014 } 1015 1016 List<String> utilityCarTypes = new ArrayList<>(); 1017 private static final int UTILITY_CAR_COUNT_FIELD_SIZE = 3; 1018 1019 /** 1020 * Add a list of utility cars scheduled for pick up from the route location 1021 * to the output file. The cars are blocked by destination. 1022 * 1023 * @param file Manifest or Switch List File. 1024 * @param carList List of cars for this train. 1025 * @param car The utility car. 1026 * @param isTruncate True if manifest is to be truncated 1027 * @param isManifest True if manifest, false if switch list. 1028 */ 1029 protected void pickupUtilityCars(PrintWriter file, List<Car> carList, Car car, boolean isTruncate, 1030 boolean isManifest) { 1031 // list utility cars by type, track, length, and load 1032 String[] format; 1033 if (isManifest) { 1034 format = Setup.getPickupUtilityManifestMessageFormat(); 1035 } else { 1036 format = Setup.getPickupUtilitySwitchListMessageFormat(); 1037 } 1038 if (isTruncate && isManifest) { 1039 format = Setup.createTruncatedManifestMessageFormat(format); 1040 } 1041 int count = countUtilityCars(format, carList, car, PICKUP); 1042 if (count == 0) { 1043 return; // already printed out this car type 1044 } 1045 pickUpCar(file, car, 1046 new StringBuffer(padAndTruncateIfNeeded(Setup.getPickupCarPrefix(), 1047 isManifest ? Setup.getManifestPrefixLength() : Setup.getSwitchListPrefixLength()) + 1048 SPACE + 1049 padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)), 1050 format, isManifest); 1051 } 1052 1053 /** 1054 * Add a list of utility cars scheduled for drop at the route location to 1055 * the output file. 1056 * 1057 * @param file Manifest or Switch List File. 1058 * @param carList List of cars for this train. 1059 * @param car The utility car. 1060 * @param isTruncate True if manifest is to be truncated 1061 * @param isManifest True if manifest, false if switch list. 1062 */ 1063 protected void setoutUtilityCars(PrintWriter file, List<Car> carList, Car car, boolean isTruncate, 1064 boolean isManifest) { 1065 boolean isLocal = car.isLocalMove(); 1066 StringBuffer buf; 1067 String[] format; 1068 if (isLocal && isManifest) { 1069 buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getLocalPrefix(), Setup.getManifestPrefixLength())); 1070 format = Setup.getLocalUtilityManifestMessageFormat(); 1071 } else if (!isLocal && isManifest) { 1072 buf = new StringBuffer(padAndTruncateIfNeeded(Setup.getDropCarPrefix(), Setup.getManifestPrefixLength())); 1073 format = Setup.getDropUtilityManifestMessageFormat(); 1074 } else if (isLocal && !isManifest) { 1075 buf = new StringBuffer( 1076 padAndTruncateIfNeeded(Setup.getSwitchListLocalPrefix(), Setup.getSwitchListPrefixLength())); 1077 format = Setup.getLocalUtilitySwitchListMessageFormat(); 1078 } else { 1079 buf = new StringBuffer( 1080 padAndTruncateIfNeeded(Setup.getSwitchListDropCarPrefix(), Setup.getSwitchListPrefixLength())); 1081 format = Setup.getDropUtilitySwitchListMessageFormat(); 1082 } 1083 if (isTruncate && isManifest) { 1084 format = Setup.createTruncatedManifestMessageFormat(format); 1085 } 1086 1087 int count = countUtilityCars(format, carList, car, !PICKUP); 1088 if (count == 0) { 1089 return; // already printed out this car type 1090 } 1091 buf.append(SPACE + padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)); 1092 dropCar(file, car, buf, format, isLocal, isManifest); 1093 } 1094 1095 public String pickupUtilityCars(List<Car> carList, Car car, boolean isManifest, boolean isTwoColumnTrack) { 1096 int count = countPickupUtilityCars(carList, car, isManifest); 1097 if (count == 0) { 1098 return null; 1099 } 1100 String[] format; 1101 if (isManifest && !isTwoColumnTrack) { 1102 format = Setup.getPickupUtilityManifestMessageFormat(); 1103 } else if (!isManifest && !isTwoColumnTrack) { 1104 format = Setup.getPickupUtilitySwitchListMessageFormat(); 1105 } else if (isManifest && isTwoColumnTrack) { 1106 format = Setup.getPickupTwoColumnByTrackUtilityManifestMessageFormat(); 1107 } else { 1108 format = Setup.getPickupTwoColumnByTrackUtilitySwitchListMessageFormat(); 1109 } 1110 StringBuffer buf = new StringBuffer(SPACE + padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)); 1111 for (String attribute : format) { 1112 buf.append(getCarAttribute(car, attribute, PICKUP, !LOCAL)); 1113 } 1114 return buf.toString(); 1115 } 1116 1117 public int countPickupUtilityCars(List<Car> carList, Car car, boolean isManifest) { 1118 // list utility cars by type, track, length, and load 1119 String[] format; 1120 if (isManifest) { 1121 format = Setup.getPickupUtilityManifestMessageFormat(); 1122 } else { 1123 format = Setup.getPickupUtilitySwitchListMessageFormat(); 1124 } 1125 return countUtilityCars(format, carList, car, PICKUP); 1126 } 1127 1128 /** 1129 * For the Conductor and Yardmaster windows. 1130 * 1131 * @param carList List of cars for this train. 1132 * @param car The utility car. 1133 * @param isLocal True if local move. 1134 * @param isManifest True if manifest, false if switch list. 1135 * @return A string representing the work of identical utility cars. 1136 */ 1137 public String setoutUtilityCars(List<Car> carList, Car car, boolean isLocal, boolean isManifest) { 1138 return setoutUtilityCars(carList, car, isLocal, isManifest, !IS_TWO_COLUMN_TRACK); 1139 } 1140 1141 protected String setoutUtilityCars(List<Car> carList, Car car, boolean isLocal, boolean isManifest, 1142 boolean isTwoColumnTrack) { 1143 int count = countSetoutUtilityCars(carList, car, isLocal, isManifest); 1144 if (count == 0) { 1145 return null; 1146 } 1147 // list utility cars by type, track, length, and load 1148 String[] format; 1149 if (isLocal && isManifest && !isTwoColumnTrack) { 1150 format = Setup.getLocalUtilityManifestMessageFormat(); 1151 } else if (isLocal && !isManifest && !isTwoColumnTrack) { 1152 format = Setup.getLocalUtilitySwitchListMessageFormat(); 1153 } else if (!isLocal && !isManifest && !isTwoColumnTrack) { 1154 format = Setup.getDropUtilitySwitchListMessageFormat(); 1155 } else if (!isLocal && isManifest && !isTwoColumnTrack) { 1156 format = Setup.getDropUtilityManifestMessageFormat(); 1157 } else if (isManifest && isTwoColumnTrack) { 1158 format = Setup.getDropTwoColumnByTrackUtilityManifestMessageFormat(); 1159 } else { 1160 format = Setup.getDropTwoColumnByTrackUtilitySwitchListMessageFormat(); 1161 } 1162 StringBuffer buf = new StringBuffer(SPACE + padString(Integer.toString(count), UTILITY_CAR_COUNT_FIELD_SIZE)); 1163 // TODO the Setup.Location doesn't work correctly for the conductor 1164 // window due to the fact that the car can be in the train and not 1165 // at its starting location. 1166 // Therefore we use the local true to disable it. 1167 if (car.getTrack() == null) { 1168 isLocal = true; 1169 } 1170 for (String attribute : format) { 1171 buf.append(getCarAttribute(car, attribute, !PICKUP, isLocal)); 1172 } 1173 return buf.toString(); 1174 } 1175 1176 public int countSetoutUtilityCars(List<Car> carList, Car car, boolean isLocal, boolean isManifest) { 1177 // list utility cars by type, track, length, and load 1178 String[] format; 1179 if (isLocal && isManifest) { 1180 format = Setup.getLocalUtilityManifestMessageFormat(); 1181 } else if (isLocal && !isManifest) { 1182 format = Setup.getLocalUtilitySwitchListMessageFormat(); 1183 } else if (!isLocal && !isManifest) { 1184 format = Setup.getDropUtilitySwitchListMessageFormat(); 1185 } else { 1186 format = Setup.getDropUtilityManifestMessageFormat(); 1187 } 1188 return countUtilityCars(format, carList, car, !PICKUP); 1189 } 1190 1191 /** 1192 * Scans the car list for utility cars that have the same attributes as the 1193 * car provided. Returns 0 if this car type has already been processed, 1194 * otherwise the number of cars with the same attribute. 1195 * 1196 * @param format Message format. 1197 * @param carList List of cars for this train 1198 * @param car The utility car. 1199 * @param isPickup True if pick up, false if set out. 1200 * @return 0 if the car type has already been processed 1201 */ 1202 protected int countUtilityCars(String[] format, List<Car> carList, Car car, boolean isPickup) { 1203 int count = 0; 1204 // figure out if the user wants to show the car's length 1205 boolean showLength = showUtilityCarLength(format); 1206 // figure out if the user want to show the car's loads 1207 boolean showLoad = showUtilityCarLoad(format); 1208 boolean showLocation = false; 1209 boolean showDestination = false; 1210 String carType = car.getTypeName().split(HYPHEN)[0]; 1211 String carAttributes; 1212 // Note for car pick up: type, id, track name. For set out type, track 1213 // name, id (reversed). 1214 if (isPickup) { 1215 carAttributes = carType + car.getRouteLocationId() + car.getSplitTrackName(); 1216 showDestination = showUtilityCarDestination(format); 1217 if (showDestination) { 1218 carAttributes = carAttributes + car.getRouteDestinationId(); 1219 } 1220 } else { 1221 // set outs and local moves 1222 carAttributes = carType + car.getSplitDestinationTrackName() + car.getRouteDestinationId(); 1223 showLocation = showUtilityCarLocation(format); 1224 if (showLocation && car.getTrack() != null) { 1225 carAttributes = carAttributes + car.getRouteLocationId(); 1226 } 1227 } 1228 if (car.isLocalMove()) { 1229 carAttributes = carAttributes + car.getSplitTrackName(); 1230 } 1231 if (showLength) { 1232 carAttributes = carAttributes + car.getLength(); 1233 } 1234 if (showLoad) { 1235 carAttributes = carAttributes + car.getLoadName(); 1236 } 1237 // have we already done this car type? 1238 if (!utilityCarTypes.contains(carAttributes)) { 1239 utilityCarTypes.add(carAttributes); // don't do this type again 1240 // determine how many cars of this type 1241 for (Car c : carList) { 1242 if (!c.isUtility()) { 1243 continue; 1244 } 1245 String cType = c.getTypeName().split(HYPHEN)[0]; 1246 if (!cType.equals(carType)) { 1247 continue; 1248 } 1249 if (showLength && !c.getLength().equals(car.getLength())) { 1250 continue; 1251 } 1252 if (showLoad && !c.getLoadName().equals(car.getLoadName())) { 1253 continue; 1254 } 1255 if (showLocation && !c.getRouteLocationId().equals(car.getRouteLocationId())) { 1256 continue; 1257 } 1258 if (showDestination && !c.getRouteDestinationId().equals(car.getRouteDestinationId())) { 1259 continue; 1260 } 1261 if (car.isLocalMove() ^ c.isLocalMove()) { 1262 continue; 1263 } 1264 if (isPickup && 1265 c.getRouteLocation() == car.getRouteLocation() && 1266 c.getSplitTrackName().equals(car.getSplitTrackName())) { 1267 count++; 1268 } 1269 if (!isPickup && 1270 c.getRouteDestination() == car.getRouteDestination() && 1271 c.getSplitDestinationTrackName().equals(car.getSplitDestinationTrackName()) && 1272 (c.getSplitTrackName().equals(car.getSplitTrackName()) || !c.isLocalMove())) { 1273 count++; 1274 } 1275 } 1276 } 1277 return count; 1278 } 1279 1280 public void clearUtilityCarTypes() { 1281 utilityCarTypes.clear(); 1282 } 1283 1284 private boolean showUtilityCarLength(String[] mFormat) { 1285 return showUtilityCarAttribute(Setup.LENGTH, mFormat); 1286 } 1287 1288 private boolean showUtilityCarLoad(String[] mFormat) { 1289 return showUtilityCarAttribute(Setup.LOAD, mFormat); 1290 } 1291 1292 private boolean showUtilityCarLocation(String[] mFormat) { 1293 return showUtilityCarAttribute(Setup.LOCATION, mFormat); 1294 } 1295 1296 private boolean showUtilityCarDestination(String[] mFormat) { 1297 return showUtilityCarAttribute(Setup.DESTINATION, mFormat) || 1298 showUtilityCarAttribute(Setup.DEST_TRACK, mFormat); 1299 } 1300 1301 private boolean showUtilityCarAttribute(String string, String[] mFormat) { 1302 for (String s : mFormat) { 1303 if (s.equals(string)) { 1304 return true; 1305 } 1306 } 1307 return false; 1308 } 1309 1310 /** 1311 * Writes a line to the build report file 1312 * 1313 * @param file build report file 1314 * @param level print level 1315 * @param string string to write 1316 */ 1317 public static void addLine(PrintWriter file, String level, String string) { 1318 log.debug("addLine: {}", string); 1319 if (file != null) { 1320 String[] lines = string.split(NEW_LINE); 1321 for (String line : lines) { 1322 printLine(file, level, line); 1323 } 1324 } 1325 } 1326 1327 // only used by build report 1328 private static void printLine(PrintWriter file, String level, String string) { 1329 int lineLengthMax = getLineLength(Setup.PORTRAIT, Setup.MONOSPACED, Font.PLAIN, Setup.getBuildReportFontSize()); 1330 if (string.length() > lineLengthMax) { 1331 String[] words = string.split(SPACE); 1332 StringBuffer sb = new StringBuffer(); 1333 for (String word : words) { 1334 if (sb.length() + word.length() < lineLengthMax) { 1335 sb.append(word + SPACE); 1336 } else { 1337 file.println(level + BUILD_REPORT_CHAR + SPACE + sb.toString()); 1338 sb = new StringBuffer(word + SPACE); 1339 } 1340 } 1341 string = sb.toString(); 1342 } 1343 file.println(level + BUILD_REPORT_CHAR + SPACE + string); 1344 } 1345 1346 /** 1347 * Writes string to file. No line length wrap or protection. 1348 * 1349 * @param file The File to write to. 1350 * @param string The string to write. 1351 */ 1352 protected void addLine(PrintWriter file, String string) { 1353 log.debug("addLine: {}", string); 1354 if (file != null) { 1355 file.println(string); 1356 } 1357 } 1358 1359 /** 1360 * Writes a string to a file. Checks for string length, and will 1361 * automatically wrap lines. 1362 * 1363 * @param file The File to write to. 1364 * @param string The string to write. 1365 * @param isManifest set true for manifest page orientation, false for 1366 * switch list orientation 1367 */ 1368 protected void newLine(PrintWriter file, String string, boolean isManifest) { 1369 String[] lines = string.split(NEW_LINE); 1370 for (String line : lines) { 1371 String[] words = line.split(SPACE); 1372 StringBuffer sb = new StringBuffer(); 1373 for (String word : words) { 1374 if (checkStringLength(sb.toString() + word, isManifest)) { 1375 sb.append(word + SPACE); 1376 } else { 1377 sb.setLength(sb.length() - 1); // remove last space added to string 1378 addLine(file, sb.toString()); 1379 sb = new StringBuffer(word + SPACE); 1380 } 1381 } 1382 if (sb.length() > 0) { 1383 sb.setLength(sb.length() - 1); // remove last space added to string 1384 } 1385 addLine(file, sb.toString()); 1386 } 1387 } 1388 1389 /** 1390 * Adds a blank line to the file. 1391 * 1392 * @param file The File to write to. 1393 */ 1394 protected void newLine(PrintWriter file) { 1395 file.println(BLANK_LINE); 1396 } 1397 1398 /** 1399 * Splits a string (example-number) as long as the second part of the string 1400 * is an integer or if the first character after the hyphen is a left 1401 * parenthesis "(". 1402 * 1403 * @param name The string to split if necessary. 1404 * @return First half of the string. 1405 */ 1406 public static String splitString(String name) { 1407 String[] splitname = name.split(HYPHEN); 1408 // is the hyphen followed by a number or left parenthesis? 1409 if (splitname.length > 1 && !splitname[1].startsWith("(")) { 1410 try { 1411 Integer.parseInt(splitname[1]); 1412 } catch (NumberFormatException e) { 1413 // no return full name 1414 return name.trim(); 1415 } 1416 } 1417 return splitname[0].trim(); 1418 } 1419 1420 /** 1421 * Splits a string if there's a hyphen followed by a left parenthesis "-(". 1422 * 1423 * @param name the string to split 1424 * @return First half of the string. 1425 */ 1426 public static String splitStringLeftParenthesis(String name) { 1427 String[] splitname = name.split(HYPHEN); 1428 if (splitname.length > 1 && splitname[1].startsWith("(")) { 1429 return splitname[0].trim(); 1430 } 1431 return name.trim(); 1432 } 1433 1434 // returns true if there's work at location 1435 protected boolean isThereWorkAtLocation(List<Car> carList, List<Engine> engList, RouteLocation rl) { 1436 if (carList != null) { 1437 for (Car car : carList) { 1438 if (car.getRouteLocation() == rl || car.getRouteDestination() == rl) { 1439 return true; 1440 } 1441 } 1442 } 1443 if (engList != null) { 1444 for (Engine eng : engList) { 1445 if (eng.getRouteLocation() == rl || eng.getRouteDestination() == rl) { 1446 return true; 1447 } 1448 } 1449 } 1450 return false; 1451 } 1452 1453 /** 1454 * returns true if the train has work at the location 1455 * 1456 * @param train The Train. 1457 * @param location The Location. 1458 * @return true if the train has work at the location 1459 */ 1460 public static boolean isThereWorkAtLocation(Train train, Location location) { 1461 if (isThereWorkAtLocation(train, location, InstanceManager.getDefault(CarManager.class).getList(train))) { 1462 return true; 1463 } 1464 if (isThereWorkAtLocation(train, location, InstanceManager.getDefault(EngineManager.class).getList(train))) { 1465 return true; 1466 } 1467 return false; 1468 } 1469 1470 private static boolean isThereWorkAtLocation(Train train, Location location, List<? extends RollingStock> list) { 1471 for (RollingStock rs : list) { 1472 if ((rs.getRouteLocation() != null && 1473 rs.getTrack() != null && 1474 rs.getRouteLocation().getSplitName() 1475 .equals(location.getSplitName())) || 1476 (rs.getRouteDestination() != null && 1477 rs.getRouteDestination().getSplitName().equals(location.getSplitName()))) { 1478 return true; 1479 } 1480 } 1481 return false; 1482 } 1483 1484 protected void addCarsLocationUnknown(PrintWriter file, boolean isManifest) { 1485 List<Car> cars = carManager.getCarsLocationUnknown(); 1486 if (cars.size() == 0) { 1487 return; // no cars to search for! 1488 } 1489 newLine(file); 1490 newLine(file, Setup.getMiaComment(), isManifest); 1491 if (Setup.isPrintHeadersEnabled()) { 1492 printHorizontalLine1(file, isManifest); 1493 newLine(file, SPACE + getHeader(Setup.getMissingCarMessageFormat(), false, false, false), isManifest); 1494 printHorizontalLine2(file, isManifest); 1495 } 1496 for (Car car : cars) { 1497 addSearchForCar(file, car, isManifest); 1498 } 1499 } 1500 1501 private void addSearchForCar(PrintWriter file, Car car, boolean isManifest) { 1502 StringBuffer buf = new StringBuffer(); 1503 String[] format = Setup.getMissingCarMessageFormat(); 1504 for (String attribute : format) { 1505 buf.append(getCarAttribute(car, attribute, false, false)); 1506 } 1507 newLine(file, buf.toString(), isManifest); 1508 } 1509 1510 /* 1511 * Gets an engine's attribute String. Returns empty if there isn't an 1512 * attribute and not using the tabular feature. isPickup true when engine is 1513 * being picked up. 1514 */ 1515 private String getEngineAttribute(Engine engine, String attribute, boolean isPickup) { 1516 if (!attribute.equals(Setup.BLANK)) { 1517 String s = SPACE + getEngineAttrib(engine, attribute, isPickup); 1518 if (Setup.isTabEnabled() || !s.isBlank()) { 1519 return s; 1520 } 1521 } 1522 return ""; 1523 } 1524 1525 /* 1526 * Can not use String case statement since Setup.MODEL, etc, are not fixed 1527 * strings. 1528 */ 1529 private String getEngineAttrib(Engine engine, String attribute, boolean isPickup) { 1530 if (attribute.equals(Setup.MODEL)) { 1531 return padAndTruncateIfNeeded(splitStringLeftParenthesis(engine.getModel()), 1532 InstanceManager.getDefault(EngineModels.class).getMaxNameLength()); 1533 } else if (attribute.equals(Setup.HP)) { 1534 return padAndTruncateIfNeeded(engine.getHp(), 5) + 1535 (Setup.isPrintHeadersEnabled() ? "" : TrainManifestHeaderText.getStringHeader_Hp()); 1536 } else if (attribute.equals(Setup.CONSIST)) { 1537 return padAndTruncateIfNeeded(engine.getConsistName(), 1538 InstanceManager.getDefault(ConsistManager.class).getMaxNameLength()); 1539 } else if (attribute.equals(Setup.DCC_ADDRESS)) { 1540 return padAndTruncateIfNeeded(engine.getDccAddress(), 1541 TrainManifestHeaderText.getStringHeader_DCC_Address().length()); 1542 } else if (attribute.equals(Setup.COMMENT)) { 1543 return padAndTruncateIfNeeded(engine.getComment(), engineManager.getMaxCommentLength()); 1544 } 1545 return getRollingStockAttribute(engine, attribute, isPickup, false); 1546 } 1547 1548 /* 1549 * Gets a car's attribute String. Returns empty if there isn't an attribute 1550 * and not using the tabular feature. isPickup true when car is being picked 1551 * up. isLocal true when car is performing a local move. 1552 */ 1553 private String getCarAttribute(Car car, String attribute, boolean isPickup, boolean isLocal) { 1554 if (!attribute.equals(Setup.BLANK)) { 1555 String s = SPACE + getCarAttrib(car, attribute, isPickup, isLocal); 1556 if (Setup.isTabEnabled() || !s.isBlank()) { 1557 return s; 1558 } 1559 } 1560 return ""; 1561 } 1562 1563 private String getCarAttrib(Car car, String attribute, boolean isPickup, boolean isLocal) { 1564 if (attribute.equals(Setup.LOAD)) { 1565 return ((car.isCaboose() && !Setup.isPrintCabooseLoadEnabled()) || 1566 (car.isPassenger() && !Setup.isPrintPassengerLoadEnabled())) 1567 ? padAndTruncateIfNeeded("", 1568 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()) 1569 : padAndTruncateIfNeeded(car.getLoadName().split(HYPHEN)[0], 1570 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()); 1571 } else if (attribute.equals(Setup.LOAD_TYPE)) { 1572 return padAndTruncateIfNeeded(car.getLoadType(), 1573 TrainManifestHeaderText.getStringHeader_Load_Type().length()); 1574 } else if (attribute.equals(Setup.HAZARDOUS)) { 1575 return (car.isHazardous() ? Setup.getHazardousMsg() 1576 : padAndTruncateIfNeeded("", Setup.getHazardousMsg().length())); 1577 } else if (attribute.equals(Setup.DROP_COMMENT)) { 1578 return padAndTruncateIfNeeded(car.getDropComment(), 1579 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()); 1580 } else if (attribute.equals(Setup.PICKUP_COMMENT)) { 1581 return padAndTruncateIfNeeded(car.getPickupComment(), 1582 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()); 1583 } else if (attribute.equals(Setup.KERNEL)) { 1584 return padAndTruncateIfNeeded(car.getKernelName(), 1585 InstanceManager.getDefault(KernelManager.class).getMaxNameLength()); 1586 } else if (attribute.equals(Setup.KERNEL_SIZE)) { 1587 if (car.isLead()) { 1588 return padAndTruncateIfNeeded(Integer.toString(car.getKernel().getSize()), 2); 1589 } 1590 return SPACE + SPACE; // assumes that kernel size is 99 or less 1591 } else if (attribute.equals(Setup.RWE)) { 1592 if (!car.getReturnWhenEmptyDestinationName().equals(Car.NONE)) { 1593 // format RWE destination and track name 1594 String rweAndTrackName = car.getSplitReturnWhenEmptyDestinationName(); 1595 if (!car.getReturnWhenEmptyDestTrackName().equals(Car.NONE)) { 1596 rweAndTrackName = rweAndTrackName + "," + SPACE + car.getSplitReturnWhenEmptyDestinationTrackName(); 1597 } 1598 return Setup.isPrintHeadersEnabled() 1599 ? padAndTruncateIfNeeded(rweAndTrackName, locationManager.getMaxLocationAndTrackNameLength()) 1600 : padAndTruncateIfNeeded( 1601 TrainManifestHeaderText.getStringHeader_RWE() + SPACE + rweAndTrackName, 1602 locationManager.getMaxLocationAndTrackNameLength() + 1603 TrainManifestHeaderText.getStringHeader_RWE().length() + 1604 3); 1605 } 1606 return padAndTruncateIfNeeded("", locationManager.getMaxLocationAndTrackNameLength()); 1607 } else if (attribute.equals(Setup.FINAL_DEST)) { 1608 return Setup.isPrintHeadersEnabled() 1609 ? padAndTruncateIfNeeded(car.getSplitFinalDestinationName(), 1610 locationManager.getMaxLocationNameLength()) 1611 : padAndTruncateIfNeeded( 1612 TrainManifestText.getStringFinalDestination() + 1613 SPACE + 1614 car.getSplitFinalDestinationName(), 1615 locationManager.getMaxLocationNameLength() + 1616 TrainManifestText.getStringFinalDestination().length() + 1617 1); 1618 } else if (attribute.equals(Setup.FINAL_DEST_TRACK)) { 1619 // format final destination and track name 1620 String FDAndTrackName = car.getSplitFinalDestinationName(); 1621 if (!car.getFinalDestinationTrackName().equals(Car.NONE)) { 1622 FDAndTrackName = FDAndTrackName + "," + SPACE + car.getSplitFinalDestinationTrackName(); 1623 } 1624 return Setup.isPrintHeadersEnabled() 1625 ? padAndTruncateIfNeeded(FDAndTrackName, locationManager.getMaxLocationAndTrackNameLength() + 2) 1626 : padAndTruncateIfNeeded(TrainManifestText.getStringFinalDestination() + SPACE + FDAndTrackName, 1627 locationManager.getMaxLocationAndTrackNameLength() + 1628 TrainManifestText.getStringFinalDestination().length() + 1629 3); 1630 } else if (attribute.equals(Setup.DIVISION)) { 1631 return padAndTruncateIfNeeded(car.getDivisionName(), 1632 InstanceManager.getDefault(DivisionManager.class).getMaxDivisionNameLength()); 1633 } else if (attribute.equals(Setup.BLOCKING_ORDER)) { 1634 if (car.isPassenger()) { 1635 return padAndTruncateIfNeeded(Integer.toString(car.getBlocking()), 3); 1636 } 1637 return SPACE + SPACE + SPACE; // assumes that blocking order is +/- 99 1638 } else if (attribute.equals(Setup.COMMENT)) { 1639 return padAndTruncateIfNeeded(car.getComment(), carManager.getMaxCommentLength()); 1640 } 1641 return getRollingStockAttribute(car, attribute, isPickup, isLocal); 1642 } 1643 1644 private String getRollingStockAttribute(RollingStock rs, String attribute, boolean isPickup, boolean isLocal) { 1645 try { 1646 if (attribute.equals(Setup.NUMBER)) { 1647 return padAndTruncateIfNeeded(splitString(rs.getNumber()), Control.max_len_string_print_road_number); 1648 } else if (attribute.equals(Setup.ROAD)) { 1649 String road = rs.getRoadName().split(HYPHEN)[0]; 1650 return padAndTruncateIfNeeded(road, InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1651 } else if (attribute.equals(Setup.TYPE)) { 1652 String type = rs.getTypeName().split(HYPHEN)[0]; 1653 return padAndTruncateIfNeeded(type, InstanceManager.getDefault(CarTypes.class).getMaxNameLength()); 1654 } else if (attribute.equals(Setup.LENGTH)) { 1655 return padAndTruncateIfNeeded(rs.getLength() + Setup.getLengthUnitAbv(), 1656 InstanceManager.getDefault(CarLengths.class).getMaxNameLength()); 1657 } else if (attribute.equals(Setup.WEIGHT)) { 1658 return padAndTruncateIfNeeded(Integer.toString(rs.getAdjustedWeightTons()), 1659 Control.max_len_string_weight_name) + 1660 (Setup.isPrintHeadersEnabled() ? "" : TrainManifestHeaderText.getStringHeader_Weight()); 1661 } else if (attribute.equals(Setup.COLOR)) { 1662 return padAndTruncateIfNeeded(rs.getColor(), 1663 InstanceManager.getDefault(CarColors.class).getMaxNameLength()); 1664 } else if (((attribute.equals(Setup.LOCATION)) && (isPickup || isLocal)) || 1665 (attribute.equals(Setup.TRACK) && isPickup)) { 1666 return Setup.isPrintHeadersEnabled() 1667 ? padAndTruncateIfNeeded(rs.getSplitTrackName(), 1668 locationManager.getMaxTrackNameLength()) 1669 : padAndTruncateIfNeeded( 1670 TrainManifestText.getStringFrom() + SPACE + rs.getSplitTrackName(), 1671 TrainManifestText.getStringFrom().length() + 1672 locationManager.getMaxTrackNameLength() + 1673 1); 1674 } else if (attribute.equals(Setup.LOCATION) && !isPickup && !isLocal) { 1675 return Setup.isPrintHeadersEnabled() 1676 ? padAndTruncateIfNeeded(rs.getSplitLocationName(), 1677 locationManager.getMaxLocationNameLength()) 1678 : padAndTruncateIfNeeded( 1679 TrainManifestText.getStringFrom() + SPACE + rs.getSplitLocationName(), 1680 locationManager.getMaxLocationNameLength() + 1681 TrainManifestText.getStringFrom().length() + 1682 1); 1683 } else if (attribute.equals(Setup.DESTINATION) && isPickup) { 1684 if (Setup.isPrintHeadersEnabled()) { 1685 return padAndTruncateIfNeeded(rs.getSplitDestinationName(), 1686 locationManager.getMaxLocationNameLength()); 1687 } 1688 if (Setup.isTabEnabled()) { 1689 return padAndTruncateIfNeeded( 1690 TrainManifestText.getStringDest() + SPACE + rs.getSplitDestinationName(), 1691 TrainManifestText.getStringDest().length() + 1692 locationManager.getMaxLocationNameLength() + 1693 1); 1694 } else { 1695 return TrainManifestText.getStringDestination() + 1696 SPACE + 1697 rs.getSplitDestinationName(); 1698 } 1699 } else if ((attribute.equals(Setup.DESTINATION) || attribute.equals(Setup.TRACK)) && !isPickup) { 1700 return Setup.isPrintHeadersEnabled() 1701 ? padAndTruncateIfNeeded(rs.getSplitDestinationTrackName(), 1702 locationManager.getMaxTrackNameLength()) 1703 : padAndTruncateIfNeeded( 1704 TrainManifestText.getStringTo() + 1705 SPACE + 1706 rs.getSplitDestinationTrackName(), 1707 locationManager.getMaxTrackNameLength() + 1708 TrainManifestText.getStringTo().length() + 1709 1); 1710 } else if (attribute.equals(Setup.DEST_TRACK)) { 1711 // format destination name and destination track name 1712 String destAndTrackName = 1713 rs.getSplitDestinationName() + "," + SPACE + rs.getSplitDestinationTrackName(); 1714 return Setup.isPrintHeadersEnabled() 1715 ? padAndTruncateIfNeeded(destAndTrackName, 1716 locationManager.getMaxLocationAndTrackNameLength() + 2) 1717 : padAndTruncateIfNeeded(TrainManifestText.getStringDest() + SPACE + destAndTrackName, 1718 locationManager.getMaxLocationAndTrackNameLength() + 1719 TrainManifestText.getStringDest().length() + 1720 3); 1721 } else if (attribute.equals(Setup.OWNER)) { 1722 return padAndTruncateIfNeeded(rs.getOwnerName(), 1723 InstanceManager.getDefault(CarOwners.class).getMaxNameLength()); 1724 } else if (attribute.equals(Setup.LAST_TRAIN)) { 1725 String lastTrainName = padAndTruncateIfNeeded(rs.getLastTrainName(), 1726 InstanceManager.getDefault(TrainManager.class).getMaxTrainNameLength()); 1727 return Setup.isPrintHeadersEnabled() ? lastTrainName 1728 : TrainManifestHeaderText.getStringHeader_Last_Train() + SPACE + lastTrainName; 1729 } 1730 // the three utility attributes that don't get printed but need to 1731 // be tabbed out 1732 else if (attribute.equals(Setup.NO_NUMBER)) { 1733 return padAndTruncateIfNeeded("", 1734 Control.max_len_string_print_road_number - (UTILITY_CAR_COUNT_FIELD_SIZE + 1)); 1735 } else if (attribute.equals(Setup.NO_ROAD)) { 1736 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1737 } else if (attribute.equals(Setup.NO_COLOR)) { 1738 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarColors.class).getMaxNameLength()); 1739 } // there are four truncated manifest attributes 1740 else if (attribute.equals(Setup.NO_DEST_TRACK)) { 1741 return Setup.isPrintHeadersEnabled() 1742 ? padAndTruncateIfNeeded("", locationManager.getMaxLocationAndTrackNameLength() + 1) 1743 : ""; 1744 } else if ((attribute.equals(Setup.NO_LOCATION) && !isPickup) || 1745 (attribute.equals(Setup.NO_DESTINATION) && isPickup)) { 1746 return Setup.isPrintHeadersEnabled() 1747 ? padAndTruncateIfNeeded("", locationManager.getMaxLocationNameLength()) 1748 : ""; 1749 } else if (attribute.equals(Setup.NO_TRACK) || 1750 attribute.equals(Setup.NO_LOCATION) || 1751 attribute.equals(Setup.NO_DESTINATION)) { 1752 return Setup.isPrintHeadersEnabled() 1753 ? padAndTruncateIfNeeded("", locationManager.getMaxTrackNameLength()) 1754 : ""; 1755 } else if (attribute.equals(Setup.TAB)) { 1756 return createTabIfNeeded(Setup.getTab1Length() - 1); 1757 } else if (attribute.equals(Setup.TAB2)) { 1758 return createTabIfNeeded(Setup.getTab2Length() - 1); 1759 } else if (attribute.equals(Setup.TAB3)) { 1760 return createTabIfNeeded(Setup.getTab3Length() - 1); 1761 } 1762 // something isn't right! 1763 return Bundle.getMessage("ErrorPrintOptions", attribute); 1764 1765 } catch (ArrayIndexOutOfBoundsException e) { 1766 if (attribute.equals(Setup.ROAD)) { 1767 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1768 } else if (attribute.equals(Setup.TYPE)) { 1769 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarTypes.class).getMaxNameLength()); 1770 } 1771 // something isn't right! 1772 return Bundle.getMessage("ErrorPrintOptions", attribute); 1773 } 1774 } 1775 1776 /** 1777 * Two column header format. Left side pick ups, right side set outs 1778 * 1779 * @param file Manifest or switch list File. 1780 * @param isManifest True if manifest, false if switch list. 1781 */ 1782 public void printEngineHeader(PrintWriter file, boolean isManifest) { 1783 int lineLength = getLineLength(isManifest); 1784 printHorizontalLine(file, isManifest); 1785 if (Setup.isPrintHeadersEnabled()) { 1786 if (!Setup.getPickupEnginePrefix().isBlank() || !Setup.getDropEnginePrefix().isBlank()) { 1787 // center engine pick up and set out text 1788 String s = padAndTruncate(tabString(Setup.getPickupEnginePrefix().trim(), 1789 lineLength / 4 - Setup.getPickupEnginePrefix().length() / 2), lineLength / 2) + 1790 VERTICAL_LINE_CHAR + 1791 tabString(Setup.getDropEnginePrefix(), 1792 lineLength / 4 - Setup.getDropEnginePrefix().length() / 2); 1793 s = padAndTruncate(s, lineLength); 1794 addLine(file, s); 1795 printHorizontalLine1(file, isManifest); 1796 } 1797 1798 String s = padAndTruncate(getPickupEngineHeader(), lineLength / 2); 1799 s = padAndTruncate(s + VERTICAL_LINE_CHAR + getDropEngineHeader(), lineLength); 1800 addLine(file, s); 1801 printHorizontalLine2(file, isManifest); 1802 } 1803 } 1804 1805 public void printPickupEngineHeader(PrintWriter file, boolean isManifest) { 1806 int lineLength = getLineLength(isManifest); 1807 printHorizontalLine1(file, isManifest); 1808 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getPickupEngineHeader(), 1809 lineLength); 1810 addLine(file, s); 1811 printHorizontalLine2(file, isManifest); 1812 } 1813 1814 public void printDropEngineHeader(PrintWriter file, boolean isManifest) { 1815 int lineLength = getLineLength(isManifest); 1816 printHorizontalLine1(file, isManifest); 1817 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getDropEngineHeader(), 1818 lineLength); 1819 addLine(file, s); 1820 printHorizontalLine2(file, isManifest); 1821 } 1822 1823 /** 1824 * Prints the two column header for cars. Left side pick ups, right side set 1825 * outs. 1826 * 1827 * @param file Manifest or Switch List File 1828 * @param isManifest True if manifest, false if switch list. 1829 * @param isTwoColumnTrack True if two column format using track names. 1830 */ 1831 public void printCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1832 int lineLength = getLineLength(isManifest); 1833 printHorizontalLine(file, isManifest); 1834 if (Setup.isPrintHeadersEnabled()) { 1835 // center pick up and set out text 1836 String s = padAndTruncate( 1837 tabString(Setup.getPickupCarPrefix(), lineLength / 4 - Setup.getPickupCarPrefix().length() / 2), 1838 lineLength / 2) + 1839 VERTICAL_LINE_CHAR + 1840 tabString(Setup.getDropCarPrefix(), lineLength / 4 - Setup.getDropCarPrefix().length() / 2); 1841 s = padAndTruncate(s, lineLength); 1842 addLine(file, s); 1843 printHorizontalLine1(file, isManifest); 1844 1845 s = padAndTruncate(getPickupCarHeader(isManifest, isTwoColumnTrack), lineLength / 2); 1846 s = padAndTruncate(s + VERTICAL_LINE_CHAR + getDropCarHeader(isManifest, isTwoColumnTrack), lineLength); 1847 addLine(file, s); 1848 printHorizontalLine2(file, isManifest); 1849 } 1850 } 1851 1852 public void printPickupCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1853 if (Setup.isPrintHeadersEnabled()) { 1854 printHorizontalLine1(file, isManifest); 1855 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + 1856 getPickupCarHeader(isManifest, isTwoColumnTrack), getLineLength(isManifest)); 1857 addLine(file, s); 1858 printHorizontalLine2(file, isManifest); 1859 } 1860 } 1861 1862 public void printDropCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1863 if (!Setup.isPrintHeadersEnabled() || getDropCarHeader(isManifest, isTwoColumnTrack).isBlank()) { 1864 return; 1865 } 1866 printHorizontalLine1(file, isManifest); 1867 String s = padAndTruncate( 1868 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getDropCarHeader(isManifest, isTwoColumnTrack), 1869 getLineLength(isManifest)); 1870 addLine(file, s); 1871 printHorizontalLine2(file, isManifest); 1872 } 1873 1874 public void printLocalCarMoveHeader(PrintWriter file, boolean isManifest) { 1875 if (!Setup.isPrintHeadersEnabled()) { 1876 return; 1877 } 1878 printHorizontalLine1(file, isManifest); 1879 String s = padAndTruncate( 1880 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getLocalMoveHeader(isManifest), 1881 getLineLength(isManifest)); 1882 addLine(file, s); 1883 printHorizontalLine2(file, isManifest); 1884 } 1885 1886 public String getPickupEngineHeader() { 1887 return getHeader(Setup.getPickupEngineMessageFormat(), PICKUP, !LOCAL, ENGINE); 1888 } 1889 1890 public String getDropEngineHeader() { 1891 return getHeader(Setup.getDropEngineMessageFormat(), !PICKUP, !LOCAL, ENGINE); 1892 } 1893 1894 public String getPickupCarHeader(boolean isManifest, boolean isTwoColumnTrack) { 1895 if (isManifest && !isTwoColumnTrack) { 1896 return getHeader(Setup.getPickupManifestMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1897 } else if (!isManifest && !isTwoColumnTrack) { 1898 return getHeader(Setup.getPickupSwitchListMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1899 } else if (isManifest && isTwoColumnTrack) { 1900 return getHeader(Setup.getPickupTwoColumnByTrackManifestMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1901 } else { 1902 return getHeader(Setup.getPickupTwoColumnByTrackSwitchListMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1903 } 1904 } 1905 1906 public String getDropCarHeader(boolean isManifest, boolean isTwoColumnTrack) { 1907 if (isManifest && !isTwoColumnTrack) { 1908 return getHeader(Setup.getDropManifestMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1909 } else if (!isManifest && !isTwoColumnTrack) { 1910 return getHeader(Setup.getDropSwitchListMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1911 } else if (isManifest && isTwoColumnTrack) { 1912 return getHeader(Setup.getDropTwoColumnByTrackManifestMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1913 } else { 1914 return getHeader(Setup.getDropTwoColumnByTrackSwitchListMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1915 } 1916 } 1917 1918 public String getLocalMoveHeader(boolean isManifest) { 1919 if (isManifest) { 1920 return getHeader(Setup.getLocalManifestMessageFormat(), !PICKUP, LOCAL, !ENGINE); 1921 } else { 1922 return getHeader(Setup.getLocalSwitchListMessageFormat(), !PICKUP, LOCAL, !ENGINE); 1923 } 1924 } 1925 1926 private String getHeader(String[] format, boolean isPickup, boolean isLocal, boolean isEngine) { 1927 StringBuffer buf = new StringBuffer(); 1928 for (String attribute : format) { 1929 if (attribute.equals(Setup.BLANK)) { 1930 continue; 1931 } 1932 if (attribute.equals(Setup.ROAD)) { 1933 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Road(), 1934 InstanceManager.getDefault(CarRoads.class).getMaxNameLength()) + SPACE); 1935 } else if (attribute.equals(Setup.NUMBER) && !isEngine) { 1936 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Number(), 1937 Control.max_len_string_print_road_number) + SPACE); 1938 } else if (attribute.equals(Setup.NUMBER) && isEngine) { 1939 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_EngineNumber(), 1940 Control.max_len_string_print_road_number) + SPACE); 1941 } else if (attribute.equals(Setup.TYPE)) { 1942 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Type(), 1943 InstanceManager.getDefault(CarTypes.class).getMaxNameLength()) + SPACE); 1944 } else if (attribute.equals(Setup.MODEL)) { 1945 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Model(), 1946 InstanceManager.getDefault(EngineModels.class).getMaxNameLength()) + SPACE); 1947 } else if (attribute.equals(Setup.HP)) { 1948 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Hp(), 1949 5) + SPACE); 1950 } else if (attribute.equals(Setup.CONSIST)) { 1951 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Consist(), 1952 InstanceManager.getDefault(ConsistManager.class).getMaxNameLength()) + SPACE); 1953 } else if (attribute.equals(Setup.DCC_ADDRESS)) { 1954 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_DCC_Address(), 1955 TrainManifestHeaderText.getStringHeader_DCC_Address().length()) + SPACE); 1956 } else if (attribute.equals(Setup.KERNEL)) { 1957 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Kernel(), 1958 InstanceManager.getDefault(KernelManager.class).getMaxNameLength()) + SPACE); 1959 } else if (attribute.equals(Setup.KERNEL_SIZE)) { 1960 buf.append(" "); // assume kernel size is 99 or less 1961 } else if (attribute.equals(Setup.LOAD)) { 1962 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Load(), 1963 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()) + SPACE); 1964 } else if (attribute.equals(Setup.LOAD_TYPE)) { 1965 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Load_Type(), 1966 TrainManifestHeaderText.getStringHeader_Load_Type().length()) + SPACE); 1967 } else if (attribute.equals(Setup.COLOR)) { 1968 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Color(), 1969 InstanceManager.getDefault(CarColors.class).getMaxNameLength()) + SPACE); 1970 } else if (attribute.equals(Setup.OWNER)) { 1971 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Owner(), 1972 InstanceManager.getDefault(CarOwners.class).getMaxNameLength()) + SPACE); 1973 } else if (attribute.equals(Setup.LENGTH)) { 1974 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Length(), 1975 InstanceManager.getDefault(CarLengths.class).getMaxNameLength()) + SPACE); 1976 } else if (attribute.equals(Setup.WEIGHT)) { 1977 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Weight(), 1978 Control.max_len_string_weight_name) + SPACE); 1979 } else if (attribute.equals(Setup.TRACK)) { 1980 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Track(), 1981 locationManager.getMaxTrackNameLength()) + SPACE); 1982 } else if (attribute.equals(Setup.LOCATION) && (isPickup || isLocal)) { 1983 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Location(), 1984 locationManager.getMaxTrackNameLength()) + SPACE); 1985 } else if (attribute.equals(Setup.LOCATION) && !isPickup) { 1986 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Location(), 1987 locationManager.getMaxLocationNameLength()) + SPACE); 1988 } else if (attribute.equals(Setup.DESTINATION) && !isPickup) { 1989 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Destination(), 1990 locationManager.getMaxTrackNameLength()) + SPACE); 1991 } else if (attribute.equals(Setup.DESTINATION) && isPickup) { 1992 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Destination(), 1993 locationManager.getMaxLocationNameLength()) + SPACE); 1994 } else if (attribute.equals(Setup.DEST_TRACK)) { 1995 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Dest_Track(), 1996 locationManager.getMaxLocationAndTrackNameLength() + 2) + SPACE); 1997 } else if (attribute.equals(Setup.FINAL_DEST)) { 1998 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Final_Dest(), 1999 locationManager.getMaxLocationNameLength()) + SPACE); 2000 } else if (attribute.equals(Setup.FINAL_DEST_TRACK)) { 2001 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Final_Dest_Track(), 2002 locationManager.getMaxLocationAndTrackNameLength() + 2) + SPACE); 2003 } else if (attribute.equals(Setup.HAZARDOUS)) { 2004 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Hazardous(), 2005 Setup.getHazardousMsg().length()) + SPACE); 2006 } else if (attribute.equals(Setup.RWE)) { 2007 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_RWE(), 2008 locationManager.getMaxLocationAndTrackNameLength()) + SPACE); 2009 } else if (attribute.equals(Setup.COMMENT)) { 2010 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Comment(), 2011 isEngine ? engineManager.getMaxCommentLength() : carManager.getMaxCommentLength()) + SPACE); 2012 } else if (attribute.equals(Setup.DROP_COMMENT)) { 2013 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Drop_Comment(), 2014 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()) + SPACE); 2015 } else if (attribute.equals(Setup.PICKUP_COMMENT)) { 2016 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Pickup_Comment(), 2017 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()) + SPACE); 2018 } else if (attribute.equals(Setup.DIVISION)) { 2019 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Division(), 2020 InstanceManager.getDefault(DivisionManager.class).getMaxDivisionNameLength()) + SPACE); 2021 } else if (attribute.equals(Setup.BLOCKING_ORDER)) { 2022 buf.append(" "); // assume blocking order +/- 99 2023 } else if (attribute.equals(Setup.LAST_TRAIN)) { 2024 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Last_Train(), 2025 InstanceManager.getDefault(TrainManager.class).getMaxTrainNameLength()) + SPACE); 2026 } else if (attribute.equals(Setup.TAB)) { 2027 buf.append(createTabIfNeeded(Setup.getTab1Length())); 2028 } else if (attribute.equals(Setup.TAB2)) { 2029 buf.append(createTabIfNeeded(Setup.getTab2Length())); 2030 } else if (attribute.equals(Setup.TAB3)) { 2031 buf.append(createTabIfNeeded(Setup.getTab3Length())); 2032 } else { 2033 buf.append(attribute + SPACE); 2034 } 2035 } 2036 return buf.toString().stripTrailing(); 2037 } 2038 2039 protected void printTrackNameHeader(PrintWriter file, String trackName, boolean isManifest) { 2040 printHorizontalLine(file, isManifest); 2041 int lineLength = getLineLength(isManifest); 2042 String s = padAndTruncate(tabString(trackName.trim(), lineLength / 4 - trackName.trim().length() / 2), 2043 lineLength / 2) + 2044 VERTICAL_LINE_CHAR + 2045 tabString(trackName.trim(), lineLength / 4 - trackName.trim().length() / 2); 2046 s = padAndTruncate(s, lineLength); 2047 addLine(file, s); 2048 if (Setup.isPrintHeaderLine3Enabled()) { 2049 printHorizontalLine(file, isManifest); 2050 } 2051 } 2052 2053 public void printHorizontalLine1(PrintWriter file, boolean isManifest) { 2054 if (Setup.isPrintHeaderLine1Enabled()) { 2055 printHorizontalLine(file, isManifest); 2056 } 2057 } 2058 2059 public void printHorizontalLine2(PrintWriter file, boolean isManifest) { 2060 if (Setup.isPrintHeaderLine2Enabled()) { 2061 printHorizontalLine(file, isManifest); 2062 } 2063 } 2064 2065 public void printHorizontalLine3(PrintWriter file, boolean isManifest) { 2066 if (Setup.isPrintHeadersEnabled() && Setup.isPrintHeaderLine3Enabled() || 2067 !Setup.getManifestFormat().equals(Setup.STANDARD_FORMAT)) { 2068 printHorizontalLine(file, isManifest); 2069 } 2070 } 2071 2072 /** 2073 * Prints a line across the entire page. 2074 * 2075 * @param file The File to print to. 2076 * @param isManifest True if manifest, false if switch list. 2077 */ 2078 public void printHorizontalLine(PrintWriter file, boolean isManifest) { 2079 printHorizontalLine(file, 0, getLineLength(isManifest)); 2080 } 2081 2082 public void printHorizontalLine(PrintWriter file, int start, int end) { 2083 StringBuffer sb = new StringBuffer(); 2084 while (start-- > 0) { 2085 sb.append(SPACE); 2086 } 2087 while (end-- > 0) { 2088 sb.append(HORIZONTAL_LINE_CHAR); 2089 } 2090 addLine(file, sb.toString()); 2091 } 2092 2093 public static String getISO8601Date(boolean isModelYear) { 2094 Calendar calendar = Calendar.getInstance(); 2095 // use the JMRI Timebase (which may be a fast clock). 2096 calendar.setTime(jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime()); 2097 if (isModelYear && !Setup.getYearModeled().isEmpty()) { 2098 try { 2099 calendar.set(Calendar.YEAR, Integer.parseInt(Setup.getYearModeled().trim())); 2100 } catch (NumberFormatException e) { 2101 return Setup.getYearModeled(); 2102 } 2103 } 2104 return (new StdDateFormat()).format(calendar.getTime()); 2105 } 2106 2107 public static String getDate(Date date) { 2108 SimpleDateFormat format = new SimpleDateFormat("M/dd/yyyy HH:mm"); // NOI18N 2109 if (Setup.is12hrFormatEnabled()) { 2110 format = new SimpleDateFormat("M/dd/yyyy hh:mm a"); // NOI18N 2111 } 2112 return format.format(date); 2113 } 2114 2115 public static String getDate(boolean isModelYear) { 2116 Calendar calendar = Calendar.getInstance(); 2117 // use the JMRI Timebase (which may be a fast clock). 2118 calendar.setTime(jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime()); 2119 if (isModelYear && !Setup.getYearModeled().equals(Setup.NONE)) { 2120 try { 2121 calendar.set(Calendar.YEAR, Integer.parseInt(Setup.getYearModeled().trim())); 2122 } catch (NumberFormatException e) { 2123 return Setup.getYearModeled(); 2124 } 2125 } 2126 return TrainCommon.getDate(calendar.getTime()); 2127 } 2128 2129 public static Date convertStringToDate(String date) { 2130 if (!date.isBlank()) { 2131 // create a date object from the string. 2132 try { 2133 // try MM/dd/yyyy HH:mm:ss. 2134 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); // NOI18N 2135 return formatter.parse(date); 2136 } catch (java.text.ParseException pe1) { 2137 // try the old 12 hour format (no seconds). 2138 try { 2139 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy hh:mmaa"); // NOI18N 2140 return formatter.parse(date); 2141 } catch (java.text.ParseException pe2) { 2142 try { 2143 // try 24hour clock. 2144 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm"); // NOI18N 2145 return formatter.parse(date); 2146 } catch (java.text.ParseException pe3) { 2147 log.debug("Not able to parse date: {}", date); 2148 } 2149 } 2150 } 2151 } 2152 return null; // there was no date specified. 2153 } 2154 2155 /* 2156 * Converts String time DAYS:HH:MM and DAYS:HH:MM AM/PM to minutes from 2157 * midnight. Note that the string time could be blank, and in that case 2158 * returns 0 minutes. 2159 */ 2160 public static int convertStringTime(String time) { 2161 int minutes = 0; 2162 boolean hrFormat = false; 2163 String[] splitTimePM = time.split(SPACE); 2164 if (splitTimePM.length > 1) { 2165 hrFormat = true; 2166 if (splitTimePM[1].equals(Bundle.getMessage("PM"))) { 2167 minutes = 12 * 60; 2168 } 2169 } 2170 String[] splitTime = splitTimePM[0].split(":"); 2171 2172 if (splitTime.length > 2) { 2173 // days:hrs:minutes 2174 if (hrFormat && splitTime[1].equals("12")) { 2175 splitTime[1] = "00"; 2176 } 2177 minutes += 24 * 60 * Integer.parseInt(splitTime[0]); 2178 minutes += 60 * Integer.parseInt(splitTime[1]); 2179 minutes += Integer.parseInt(splitTime[2]); 2180 } else if (splitTime.length == 2){ 2181 // hrs:minutes 2182 if (hrFormat && splitTime[0].equals("12")) { 2183 splitTime[0] = "00"; 2184 } 2185 minutes += 60 * Integer.parseInt(splitTime[0]); 2186 minutes += Integer.parseInt(splitTime[1]); 2187 } 2188 log.debug("convert time {} to minutes {}", time, minutes); 2189 return minutes; 2190 } 2191 2192 public String convertMinutesTime(int minutes) { 2193 int days = minutes / (24 * 60); 2194 int h = (minutes - (days * 24 * 60))/60; 2195 String sHours = String.format("%02d", h); 2196 int m = minutes - days * 24 * 60 - h * 60; 2197 String sMinutes = String.format("%02d", m); 2198 return Integer.toString(days) + ":" + sHours + ":" + sMinutes; 2199 } 2200 2201 2202 /** 2203 * Pads out a string by adding spaces to the end of the string, and will 2204 * remove characters from the end of the string if the string exceeds the 2205 * field size. 2206 * 2207 * @param s The string to pad. 2208 * @param fieldSize The maximum length of the string. 2209 * @return A String the specified length 2210 */ 2211 public static String padAndTruncateIfNeeded(String s, int fieldSize) { 2212 if (Setup.isTabEnabled()) { 2213 return padAndTruncate(s, fieldSize); 2214 } 2215 return s; 2216 } 2217 2218 public static String padAndTruncate(String s, int fieldSize) { 2219 s = padString(s, fieldSize); 2220 if (s.length() > fieldSize) { 2221 s = s.substring(0, fieldSize); 2222 } 2223 return s; 2224 } 2225 2226 /** 2227 * Adjusts string to be a certain number of characters by adding spaces to 2228 * the end of the string. 2229 * 2230 * @param s The string to pad 2231 * @param fieldSize The fixed length of the string. 2232 * @return A String the specified length 2233 */ 2234 public static String padString(String s, int fieldSize) { 2235 StringBuffer buf = new StringBuffer(s); 2236 while (buf.length() < fieldSize) { 2237 buf.append(SPACE); 2238 } 2239 return buf.toString(); 2240 } 2241 2242 /** 2243 * Creates a String of spaces to create a tab for text. Tabs must be 2244 * enabled. Setup.isTabEnabled() 2245 * 2246 * @param tabSize the length of tab 2247 * @return tab 2248 */ 2249 public static String createTabIfNeeded(int tabSize) { 2250 if (Setup.isTabEnabled()) { 2251 return tabString("", tabSize); 2252 } 2253 return ""; 2254 } 2255 2256 protected static String tabString(String s, int tabSize) { 2257 StringBuffer buf = new StringBuffer(); 2258 // TODO this doesn't consider the length of s string. 2259 while (buf.length() < tabSize) { 2260 buf.append(SPACE); 2261 } 2262 buf.append(s); 2263 return buf.toString(); 2264 } 2265 2266 /** 2267 * Returns the line length for manifest or switch list printout. Always an 2268 * even number. 2269 * 2270 * @param isManifest True if manifest. 2271 * @return line length for manifest or switch list. 2272 */ 2273 public static int getLineLength(boolean isManifest) { 2274 return getLineLength(isManifest ? Setup.getManifestOrientation() : Setup.getSwitchListOrientation(), 2275 Setup.getFontName(), Font.PLAIN, Setup.getManifestFontSize()); 2276 } 2277 2278 public static int getManifestHeaderLineLength() { 2279 return getLineLength(Setup.getManifestOrientation(), "SansSerif", Font.ITALIC, Setup.getManifestFontSize()); 2280 } 2281 2282 private static int getLineLength(String orientation, String fontName, int fontStyle, int fontSize) { 2283 Font font = new Font(fontName, fontStyle, fontSize); // NOI18N 2284 JLabel label = new JLabel(); 2285 FontMetrics metrics = label.getFontMetrics(font); 2286 int charwidth = metrics.charWidth('m'); 2287 if (charwidth == 0) { 2288 log.error("Line length charater width equal to zero. font size: {}, fontName: {}", fontSize, fontName); 2289 charwidth = fontSize / 2; // create a reasonable character width 2290 } 2291 // compute lines and columns within margins 2292 int charLength = getPageSize(orientation).width / charwidth; 2293 if (charLength % 2 != 0) { 2294 charLength--; // make it even 2295 } 2296 return charLength; 2297 } 2298 2299 private boolean checkStringLength(String string, boolean isManifest) { 2300 return checkStringLength(string, isManifest ? Setup.getManifestOrientation() : Setup.getSwitchListOrientation(), 2301 Setup.getFontName(), Setup.getManifestFontSize()); 2302 } 2303 2304 /** 2305 * Checks to see if the string fits on the page. 2306 * 2307 * @return false if string length is longer than page width. 2308 */ 2309 private boolean checkStringLength(String string, String orientation, String fontName, int fontSize) { 2310 // ignore text color controls when determining line length 2311 if (string.startsWith(TEXT_COLOR_START) && string.contains(TEXT_COLOR_DONE)) { 2312 string = string.substring(string.indexOf(TEXT_COLOR_DONE) + 2); 2313 } 2314 if (string.contains(TEXT_COLOR_END)) { 2315 string = string.substring(0, string.indexOf(TEXT_COLOR_END)); 2316 } 2317 Font font = new Font(fontName, Font.PLAIN, fontSize); // NOI18N 2318 JLabel label = new JLabel(); 2319 FontMetrics metrics = label.getFontMetrics(font); 2320 int stringWidth = metrics.stringWidth(string); 2321 return stringWidth <= getPageSize(orientation).width; 2322 } 2323 2324 protected static final Dimension PAPER_MARGINS = new Dimension(84, 72); 2325 2326 protected static Dimension getPageSize(String orientation) { 2327 // page size has been adjusted to account for margins of .5 2328 // Dimension(84, 72) 2329 Dimension pagesize = new Dimension(523, 720); // Portrait 8.5 x 11 2330 // landscape has .65 margins 2331 if (orientation.equals(Setup.LANDSCAPE)) { 2332 pagesize = new Dimension(702, 523); // 11 x 8.5 2333 } 2334 if (orientation.equals(Setup.HALFPAGE)) { 2335 pagesize = new Dimension(261, 720); // 4.25 x 11 2336 } 2337 if (orientation.equals(Setup.HANDHELD)) { 2338 pagesize = new Dimension(206, 720); // 3.25 x 11 2339 } 2340 return pagesize; 2341 } 2342 2343 /** 2344 * Produces a string using commas and spaces between the strings provided in 2345 * the array. Does not check for embedded commas in the string array. 2346 * 2347 * @param array The string array to be formated. 2348 * @return formated string using commas and spaces 2349 */ 2350 public static String formatStringToCommaSeparated(String[] array) { 2351 StringBuffer sbuf = new StringBuffer(""); 2352 for (String s : array) { 2353 if (s != null) { 2354 sbuf = sbuf.append(s + "," + SPACE); 2355 } 2356 } 2357 if (sbuf.length() > 2) { 2358 sbuf.setLength(sbuf.length() - 2); // remove trailing separators 2359 } 2360 return sbuf.toString(); 2361 } 2362 2363 private void addLine(PrintWriter file, StringBuffer buf, Color color) { 2364 String s = buf.toString(); 2365 if (!s.isBlank()) { 2366 addLine(file, formatColorString(s, color)); 2367 } 2368 } 2369 2370 /** 2371 * Adds HTML like color text control characters around a string. Note that 2372 * black is the standard text color, and if black is requested no control 2373 * characters are added. 2374 * 2375 * @param text the text to be modified 2376 * @param color the color the text is to be printed 2377 * @return formated text with color modifiers 2378 */ 2379 public static String formatColorString(String text, Color color) { 2380 String s = text; 2381 if (!color.equals(Color.black)) { 2382 s = TEXT_COLOR_START + ColorUtil.colorToColorName(color) + TEXT_COLOR_DONE + text + TEXT_COLOR_END; 2383 } 2384 return s; 2385 } 2386 2387 /** 2388 * Removes the color text control characters around the desired string 2389 * 2390 * @param string the string with control characters 2391 * @return pure text 2392 */ 2393 public static String getTextColorString(String string) { 2394 String text = string; 2395 if (string.contains(TEXT_COLOR_START)) { 2396 text = string.substring(0, string.indexOf(TEXT_COLOR_START)) + 2397 string.substring(string.indexOf(TEXT_COLOR_DONE) + 2); 2398 } 2399 if (text.contains(TEXT_COLOR_END)) { 2400 text = text.substring(0, text.indexOf(TEXT_COLOR_END)) + 2401 string.substring(string.indexOf(TEXT_COLOR_END) + TEXT_COLOR_END.length()); 2402 } 2403 return text; 2404 } 2405 2406 public static Color getTextColor(String string) { 2407 Color color = Color.black; 2408 if (string.contains(TEXT_COLOR_START)) { 2409 String c = string.substring(string.indexOf(TEXT_COLOR_START) + TEXT_COLOR_START.length()); 2410 c = c.substring(0, c.indexOf("\"")); 2411 color = ColorUtil.stringToColor(c); 2412 } 2413 return color; 2414 } 2415 2416 public static String getTextColorName(String string) { 2417 return ColorUtil.colorToColorName(getTextColor(string)); 2418 } 2419 2420 private static final Logger log = LoggerFactory.getLogger(TrainCommon.class); 2421}