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