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