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.HP)) { 1402 return padAndTruncateIfNeeded(engine.getHp(), 5) + 1403 (Setup.isPrintHeadersEnabled() ? "" : TrainManifestHeaderText.getStringHeader_Hp()); 1404 } else if (attribute.equals(Setup.CONSIST)) { 1405 return padAndTruncateIfNeeded(engine.getConsistName(), 1406 InstanceManager.getDefault(ConsistManager.class).getMaxNameLength()); 1407 } else if (attribute.equals(Setup.DCC_ADDRESS)) { 1408 return padAndTruncateIfNeeded(engine.getDccAddress(), 1409 TrainManifestHeaderText.getStringHeader_DCC_Address().length()); 1410 } else if (attribute.equals(Setup.COMMENT)) { 1411 return padAndTruncateIfNeeded(engine.getComment(), engineManager.getMaxCommentLength()); 1412 } 1413 return getRollingStockAttribute(engine, attribute, isPickup, false); 1414 } 1415 1416 /* 1417 * Gets a car's attribute String. Returns empty if there isn't an attribute 1418 * and not using the tabular feature. isPickup true when car is being picked 1419 * up. isLocal true when car is performing a local move. 1420 */ 1421 private String getCarAttribute(Car car, String attribute, boolean isPickup, boolean isLocal) { 1422 if (!attribute.equals(Setup.BLANK)) { 1423 String s = SPACE + getCarAttrib(car, attribute, isPickup, isLocal); 1424 if (Setup.isTabEnabled() || !s.trim().isEmpty()) { 1425 return s; 1426 } 1427 } 1428 return ""; 1429 } 1430 1431 private String getCarAttrib(Car car, String attribute, boolean isPickup, boolean isLocal) { 1432 if (attribute.equals(Setup.LOAD)) { 1433 return ((car.isCaboose() && !Setup.isPrintCabooseLoadEnabled()) || 1434 (car.isPassenger() && !Setup.isPrintPassengerLoadEnabled())) 1435 ? padAndTruncateIfNeeded("", 1436 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()) 1437 : padAndTruncateIfNeeded(car.getLoadName().split(HYPHEN)[0], 1438 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()); 1439 } else if (attribute.equals(Setup.LOAD_TYPE)) { 1440 return padAndTruncateIfNeeded(car.getLoadType(), 1441 TrainManifestHeaderText.getStringHeader_Load_Type().length()); 1442 } else if (attribute.equals(Setup.HAZARDOUS)) { 1443 return (car.isHazardous() ? Setup.getHazardousMsg() 1444 : padAndTruncateIfNeeded("", Setup.getHazardousMsg().length())); 1445 } else if (attribute.equals(Setup.DROP_COMMENT)) { 1446 return padAndTruncateIfNeeded(car.getDropComment(), 1447 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()); 1448 } else if (attribute.equals(Setup.PICKUP_COMMENT)) { 1449 return padAndTruncateIfNeeded(car.getPickupComment(), 1450 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()); 1451 } else if (attribute.equals(Setup.KERNEL)) { 1452 return padAndTruncateIfNeeded(car.getKernelName(), 1453 InstanceManager.getDefault(KernelManager.class).getMaxNameLength()); 1454 } else if (attribute.equals(Setup.KERNEL_SIZE)) { 1455 if (car.isLead()) { 1456 return padAndTruncateIfNeeded(Integer.toString(car.getKernel().getSize()), 2); 1457 } else { 1458 return SPACE + SPACE; // assumes that kernel size is 99 or less 1459 } 1460 } else if (attribute.equals(Setup.RWE)) { 1461 if (!car.getReturnWhenEmptyDestinationName().equals(Car.NONE)) { 1462 // format RWE destination and track name 1463 String rweAndTrackName = car.getSplitReturnWhenEmptyDestinationName(); 1464 if (!car.getReturnWhenEmptyDestTrackName().equals(Car.NONE)) { 1465 rweAndTrackName = rweAndTrackName + "," + SPACE + car.getSplitReturnWhenEmptyDestinationTrackName(); 1466 } 1467 return Setup.isPrintHeadersEnabled() 1468 ? padAndTruncateIfNeeded(rweAndTrackName, locationManager.getMaxLocationAndTrackNameLength()) 1469 : padAndTruncateIfNeeded( 1470 TrainManifestHeaderText.getStringHeader_RWE() + SPACE + rweAndTrackName, 1471 locationManager.getMaxLocationAndTrackNameLength() + 1472 TrainManifestHeaderText.getStringHeader_RWE().length() + 1473 3); 1474 } 1475 return padAndTruncateIfNeeded("", locationManager.getMaxLocationAndTrackNameLength()); 1476 } else if (attribute.equals(Setup.FINAL_DEST)) { 1477 return Setup.isPrintHeadersEnabled() 1478 ? padAndTruncateIfNeeded(car.getSplitFinalDestinationName(), 1479 locationManager.getMaxLocationNameLength()) 1480 : padAndTruncateIfNeeded( 1481 TrainManifestText.getStringFinalDestination() + 1482 SPACE + 1483 car.getSplitFinalDestinationName(), 1484 locationManager.getMaxLocationNameLength() + 1485 TrainManifestText.getStringFinalDestination().length() + 1486 1); 1487 } else if (attribute.equals(Setup.FINAL_DEST_TRACK)) { 1488 // format final destination and track name 1489 String FDAndTrackName = car.getSplitFinalDestinationName(); 1490 if (!car.getFinalDestinationTrackName().equals(Car.NONE)) { 1491 FDAndTrackName = FDAndTrackName + "," + SPACE + car.getSplitFinalDestinationTrackName(); 1492 } 1493 return Setup.isPrintHeadersEnabled() 1494 ? padAndTruncateIfNeeded(FDAndTrackName, locationManager.getMaxLocationAndTrackNameLength() + 2) 1495 : padAndTruncateIfNeeded(TrainManifestText.getStringFinalDestination() + SPACE + FDAndTrackName, 1496 locationManager.getMaxLocationAndTrackNameLength() + 1497 TrainManifestText.getStringFinalDestination().length() + 1498 3); 1499 } else if (attribute.equals(Setup.DIVISION)) { 1500 return padAndTruncateIfNeeded(car.getDivisionName(), 1501 InstanceManager.getDefault(DivisionManager.class).getMaxDivisionNameLength()); 1502 } else if (attribute.equals(Setup.COMMENT)) { 1503 return padAndTruncateIfNeeded(car.getComment(), carManager.getMaxCommentLength()); 1504 } 1505 return getRollingStockAttribute(car, attribute, isPickup, isLocal); 1506 } 1507 1508 private String getRollingStockAttribute(RollingStock rs, String attribute, boolean isPickup, boolean isLocal) { 1509 try { 1510 if (attribute.equals(Setup.NUMBER)) { 1511 return padAndTruncateIfNeeded(splitString(rs.getNumber()), Control.max_len_string_print_road_number); 1512 } else if (attribute.equals(Setup.ROAD)) { 1513 String road = rs.getRoadName().split(HYPHEN)[0]; 1514 return padAndTruncateIfNeeded(road, InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1515 } else if (attribute.equals(Setup.TYPE)) { 1516 String type = rs.getTypeName().split(HYPHEN)[0]; 1517 return padAndTruncateIfNeeded(type, InstanceManager.getDefault(CarTypes.class).getMaxNameLength()); 1518 } else if (attribute.equals(Setup.LENGTH)) { 1519 return padAndTruncateIfNeeded(rs.getLength() + Setup.getLengthUnitAbv(), 1520 InstanceManager.getDefault(CarLengths.class).getMaxNameLength()); 1521 } else if (attribute.equals(Setup.WEIGHT)) { 1522 return padAndTruncateIfNeeded(Integer.toString(rs.getAdjustedWeightTons()), 1523 Control.max_len_string_weight_name) + 1524 (Setup.isPrintHeadersEnabled() ? "" : TrainManifestHeaderText.getStringHeader_Weight()); 1525 } else if (attribute.equals(Setup.COLOR)) { 1526 return padAndTruncateIfNeeded(rs.getColor(), 1527 InstanceManager.getDefault(CarColors.class).getMaxNameLength()); 1528 } else if (((attribute.equals(Setup.LOCATION)) && (isPickup || isLocal)) || 1529 (attribute.equals(Setup.TRACK) && isPickup)) { 1530 return Setup.isPrintHeadersEnabled() 1531 ? padAndTruncateIfNeeded(rs.getSplitTrackName(), 1532 locationManager.getMaxTrackNameLength()) 1533 : padAndTruncateIfNeeded( 1534 TrainManifestText.getStringFrom() + SPACE + rs.getSplitTrackName(), 1535 TrainManifestText.getStringFrom().length() + 1536 locationManager.getMaxTrackNameLength() + 1537 1); 1538 } else if (attribute.equals(Setup.LOCATION) && !isPickup && !isLocal) { 1539 return Setup.isPrintHeadersEnabled() 1540 ? padAndTruncateIfNeeded(rs.getSplitLocationName(), 1541 locationManager.getMaxLocationNameLength()) 1542 : padAndTruncateIfNeeded( 1543 TrainManifestText.getStringFrom() + SPACE + rs.getSplitLocationName(), 1544 locationManager.getMaxLocationNameLength() + 1545 TrainManifestText.getStringFrom().length() + 1546 1); 1547 } else if (attribute.equals(Setup.DESTINATION) && isPickup) { 1548 if (Setup.isPrintHeadersEnabled()) { 1549 return padAndTruncateIfNeeded(rs.getSplitDestinationName(), 1550 locationManager.getMaxLocationNameLength()); 1551 } 1552 if (Setup.isTabEnabled()) { 1553 return padAndTruncateIfNeeded( 1554 TrainManifestText.getStringDest() + SPACE + rs.getSplitDestinationName(), 1555 TrainManifestText.getStringDest().length() + 1556 locationManager.getMaxLocationNameLength() + 1557 1); 1558 } else { 1559 return TrainManifestText.getStringDestination() + 1560 SPACE + 1561 rs.getSplitDestinationName(); 1562 } 1563 } else if ((attribute.equals(Setup.DESTINATION) || attribute.equals(Setup.TRACK)) && !isPickup) { 1564 return Setup.isPrintHeadersEnabled() 1565 ? padAndTruncateIfNeeded(rs.getSplitDestinationTrackName(), 1566 locationManager.getMaxTrackNameLength()) 1567 : padAndTruncateIfNeeded( 1568 TrainManifestText.getStringTo() + 1569 SPACE + 1570 rs.getSplitDestinationTrackName(), 1571 locationManager.getMaxTrackNameLength() + 1572 TrainManifestText.getStringTo().length() + 1573 1); 1574 } else if (attribute.equals(Setup.DEST_TRACK)) { 1575 // format destination name and destination track name 1576 String destAndTrackName = 1577 rs.getSplitDestinationName() + "," + SPACE + rs.getSplitDestinationTrackName(); 1578 return Setup.isPrintHeadersEnabled() 1579 ? padAndTruncateIfNeeded(destAndTrackName, 1580 locationManager.getMaxLocationAndTrackNameLength() + 2) 1581 : padAndTruncateIfNeeded(TrainManifestText.getStringDest() + SPACE + destAndTrackName, 1582 locationManager.getMaxLocationAndTrackNameLength() + 1583 TrainManifestText.getStringDest().length() + 1584 3); 1585 } else if (attribute.equals(Setup.OWNER)) { 1586 return padAndTruncateIfNeeded(rs.getOwnerName(), 1587 InstanceManager.getDefault(CarOwners.class).getMaxNameLength()); 1588 } // the three utility attributes that don't get printed but need to 1589 // be tabbed out 1590 else if (attribute.equals(Setup.NO_NUMBER)) { 1591 return padAndTruncateIfNeeded("", 1592 Control.max_len_string_print_road_number - (UTILITY_CAR_COUNT_FIELD_SIZE + 1)); 1593 } else if (attribute.equals(Setup.NO_ROAD)) { 1594 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1595 } else if (attribute.equals(Setup.NO_COLOR)) { 1596 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarColors.class).getMaxNameLength()); 1597 } // there are four truncated manifest attributes 1598 else if (attribute.equals(Setup.NO_DEST_TRACK)) { 1599 return Setup.isPrintHeadersEnabled() 1600 ? padAndTruncateIfNeeded("", locationManager.getMaxLocationAndTrackNameLength() + 1) 1601 : ""; 1602 } else if ((attribute.equals(Setup.NO_LOCATION) && !isPickup) || 1603 (attribute.equals(Setup.NO_DESTINATION) && isPickup)) { 1604 return Setup.isPrintHeadersEnabled() 1605 ? padAndTruncateIfNeeded("", locationManager.getMaxLocationNameLength()) 1606 : ""; 1607 } else if (attribute.equals(Setup.NO_TRACK) || 1608 attribute.equals(Setup.NO_LOCATION) || 1609 attribute.equals(Setup.NO_DESTINATION)) { 1610 return Setup.isPrintHeadersEnabled() 1611 ? padAndTruncateIfNeeded("", locationManager.getMaxTrackNameLength()) 1612 : ""; 1613 } else if (attribute.equals(Setup.TAB)) { 1614 return createTabIfNeeded(Setup.getTab1Length() - 1); 1615 } else if (attribute.equals(Setup.TAB2)) { 1616 return createTabIfNeeded(Setup.getTab2Length() - 1); 1617 } else if (attribute.equals(Setup.TAB3)) { 1618 return createTabIfNeeded(Setup.getTab3Length() - 1); 1619 } 1620 // something isn't right! 1621 return Bundle.getMessage("ErrorPrintOptions", attribute); 1622 1623 } catch (ArrayIndexOutOfBoundsException e) { 1624 if (attribute.equals(Setup.ROAD)) { 1625 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarRoads.class).getMaxNameLength()); 1626 } else if (attribute.equals(Setup.TYPE)) { 1627 return padAndTruncateIfNeeded("", InstanceManager.getDefault(CarTypes.class).getMaxNameLength()); 1628 } 1629 // something isn't right! 1630 return Bundle.getMessage("ErrorPrintOptions", attribute); 1631 } 1632 } 1633 1634 /** 1635 * Two column header format. Left side pick ups, right side set outs 1636 * 1637 * @param file Manifest or switch list File. 1638 * @param isManifest True if manifest, false if switch list. 1639 */ 1640 public void printEngineHeader(PrintWriter file, boolean isManifest) { 1641 int lineLength = getLineLength(isManifest); 1642 printHorizontalLine(file, 0, lineLength); 1643 if (!Setup.isPrintHeadersEnabled()) { 1644 return; 1645 } 1646 if (!Setup.getPickupEnginePrefix().trim().isEmpty() || !Setup.getDropEnginePrefix().trim().isEmpty()) { 1647 // center engine pick up and set out text 1648 String s = padAndTruncate(tabString(Setup.getPickupEnginePrefix().trim(), 1649 lineLength / 4 - Setup.getPickupEnginePrefix().length() / 2), lineLength / 2) + 1650 VERTICAL_LINE_CHAR + 1651 tabString(Setup.getDropEnginePrefix(), lineLength / 4 - Setup.getDropEnginePrefix().length() / 2); 1652 s = padAndTruncate(s, lineLength); 1653 addLine(file, s); 1654 printHorizontalLine(file, 0, lineLength); 1655 } 1656 1657 String s = padAndTruncate(getPickupEngineHeader(), lineLength / 2); 1658 s = padAndTruncate(s + VERTICAL_LINE_CHAR + getDropEngineHeader(), lineLength); 1659 addLine(file, s); 1660 printHorizontalLine(file, 0, lineLength); 1661 } 1662 1663 public void printPickupEngineHeader(PrintWriter file, boolean isManifest) { 1664 int lineLength = getLineLength(isManifest); 1665 printHorizontalLine(file, 0, lineLength); 1666 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getPickupEngineHeader(), 1667 lineLength); 1668 addLine(file, s); 1669 printHorizontalLine(file, 0, lineLength); 1670 } 1671 1672 public void printDropEngineHeader(PrintWriter file, boolean isManifest) { 1673 int lineLength = getLineLength(isManifest); 1674 printHorizontalLine(file, 0, lineLength); 1675 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getDropEngineHeader(), 1676 lineLength); 1677 addLine(file, s); 1678 printHorizontalLine(file, 0, lineLength); 1679 } 1680 1681 /** 1682 * Prints the two column header for cars. Left side pick ups, right side set 1683 * outs. 1684 * 1685 * @param file Manifest or Switch List File 1686 * @param isManifest True if manifest, false if switch list. 1687 * @param isTwoColumnTrack True if two column format using track names. 1688 */ 1689 public void printCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1690 int lineLength = getLineLength(isManifest); 1691 printHorizontalLine(file, 0, lineLength); 1692 if (!Setup.isPrintHeadersEnabled()) { 1693 return; 1694 } 1695 // center pick up and set out text 1696 String s = padAndTruncate( 1697 tabString(Setup.getPickupCarPrefix(), lineLength / 4 - Setup.getPickupCarPrefix().length() / 2), 1698 lineLength / 2) + 1699 VERTICAL_LINE_CHAR + 1700 tabString(Setup.getDropCarPrefix(), lineLength / 4 - Setup.getDropCarPrefix().length() / 2); 1701 s = padAndTruncate(s, lineLength); 1702 addLine(file, s); 1703 printHorizontalLine(file, 0, lineLength); 1704 1705 s = padAndTruncate(getPickupCarHeader(isManifest, isTwoColumnTrack), lineLength / 2); 1706 s = padAndTruncate(s + VERTICAL_LINE_CHAR + getDropCarHeader(isManifest, isTwoColumnTrack), lineLength); 1707 addLine(file, s); 1708 printHorizontalLine(file, 0, lineLength); 1709 } 1710 1711 public void printPickupCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1712 if (!Setup.isPrintHeadersEnabled()) { 1713 return; 1714 } 1715 printHorizontalLine(file, isManifest); 1716 String s = padAndTruncate(createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + 1717 getPickupCarHeader(isManifest, isTwoColumnTrack), getLineLength(isManifest)); 1718 addLine(file, s); 1719 printHorizontalLine(file, isManifest); 1720 } 1721 1722 public void printDropCarHeader(PrintWriter file, boolean isManifest, boolean isTwoColumnTrack) { 1723 if (!Setup.isPrintHeadersEnabled() || getDropCarHeader(isManifest, isTwoColumnTrack).trim().isEmpty()) { 1724 return; 1725 } 1726 printHorizontalLine(file, isManifest); 1727 String s = padAndTruncate( 1728 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getDropCarHeader(isManifest, isTwoColumnTrack), 1729 getLineLength(isManifest)); 1730 addLine(file, s); 1731 printHorizontalLine(file, isManifest); 1732 } 1733 1734 public void printLocalCarMoveHeader(PrintWriter file, boolean isManifest) { 1735 if (!Setup.isPrintHeadersEnabled()) { 1736 return; 1737 } 1738 printHorizontalLine(file, isManifest); 1739 String s = padAndTruncate( 1740 createTabIfNeeded(Setup.getManifestPrefixLength() + 1) + getLocalMoveHeader(isManifest), 1741 getLineLength(isManifest)); 1742 addLine(file, s); 1743 printHorizontalLine(file, isManifest); 1744 } 1745 1746 public String getPickupEngineHeader() { 1747 return getHeader(Setup.getPickupEngineMessageFormat(), PICKUP, !LOCAL, ENGINE); 1748 } 1749 1750 public String getDropEngineHeader() { 1751 return getHeader(Setup.getDropEngineMessageFormat(), !PICKUP, !LOCAL, ENGINE); 1752 } 1753 1754 public String getPickupCarHeader(boolean isManifest, boolean isTwoColumnTrack) { 1755 if (isManifest && !isTwoColumnTrack) { 1756 return getHeader(Setup.getPickupManifestMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1757 } else if (!isManifest && !isTwoColumnTrack) { 1758 return getHeader(Setup.getPickupSwitchListMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1759 } else if (isManifest && isTwoColumnTrack) { 1760 return getHeader(Setup.getPickupTwoColumnByTrackManifestMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1761 } else { 1762 return getHeader(Setup.getPickupTwoColumnByTrackSwitchListMessageFormat(), PICKUP, !LOCAL, !ENGINE); 1763 } 1764 } 1765 1766 public String getDropCarHeader(boolean isManifest, boolean isTwoColumnTrack) { 1767 if (isManifest && !isTwoColumnTrack) { 1768 return getHeader(Setup.getDropManifestMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1769 } else if (!isManifest && !isTwoColumnTrack) { 1770 return getHeader(Setup.getDropSwitchListMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1771 } else if (isManifest && isTwoColumnTrack) { 1772 return getHeader(Setup.getDropTwoColumnByTrackManifestMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1773 } else { 1774 return getHeader(Setup.getDropTwoColumnByTrackSwitchListMessageFormat(), !PICKUP, !LOCAL, !ENGINE); 1775 } 1776 } 1777 1778 public String getLocalMoveHeader(boolean isManifest) { 1779 if (isManifest) { 1780 return getHeader(Setup.getLocalManifestMessageFormat(), !PICKUP, LOCAL, !ENGINE); 1781 } else { 1782 return getHeader(Setup.getLocalSwitchListMessageFormat(), !PICKUP, LOCAL, !ENGINE); 1783 } 1784 } 1785 1786 private String getHeader(String[] format, boolean isPickup, boolean isLocal, boolean isEngine) { 1787 StringBuffer buf = new StringBuffer(); 1788 for (String attribute : format) { 1789 if (attribute.equals(Setup.BLANK)) { 1790 continue; 1791 } 1792 if (attribute.equals(Setup.ROAD)) { 1793 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Road(), 1794 InstanceManager.getDefault(CarRoads.class).getMaxNameLength()) + SPACE); 1795 } else if (attribute.equals(Setup.NUMBER) && !isEngine) { 1796 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Number(), 1797 Control.max_len_string_print_road_number) + SPACE); 1798 } else if (attribute.equals(Setup.NUMBER) && isEngine) { 1799 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_EngineNumber(), 1800 Control.max_len_string_print_road_number) + SPACE); 1801 } else if (attribute.equals(Setup.TYPE)) { 1802 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Type(), 1803 InstanceManager.getDefault(CarTypes.class).getMaxNameLength()) + SPACE); 1804 } else if (attribute.equals(Setup.MODEL)) { 1805 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Model(), 1806 InstanceManager.getDefault(EngineModels.class).getMaxNameLength()) + SPACE); 1807 } else if (attribute.equals(Setup.HP)) { 1808 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Hp(), 1809 5) + SPACE); 1810 } else if (attribute.equals(Setup.CONSIST)) { 1811 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Consist(), 1812 InstanceManager.getDefault(ConsistManager.class).getMaxNameLength()) + SPACE); 1813 } else if (attribute.equals(Setup.DCC_ADDRESS)) { 1814 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_DCC_Address(), 1815 TrainManifestHeaderText.getStringHeader_DCC_Address().length()) + SPACE); 1816 } else if (attribute.equals(Setup.KERNEL)) { 1817 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Kernel(), 1818 InstanceManager.getDefault(KernelManager.class).getMaxNameLength()) + SPACE); 1819 } else if (attribute.equals(Setup.KERNEL_SIZE)) { 1820 buf.append(" "); // assume kernel size is 99 or less 1821 } else if (attribute.equals(Setup.LOAD)) { 1822 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Load(), 1823 InstanceManager.getDefault(CarLoads.class).getMaxNameLength()) + SPACE); 1824 } else if (attribute.equals(Setup.LOAD_TYPE)) { 1825 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Load_Type(), 1826 TrainManifestHeaderText.getStringHeader_Load_Type().length()) + SPACE); 1827 } else if (attribute.equals(Setup.COLOR)) { 1828 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Color(), 1829 InstanceManager.getDefault(CarColors.class).getMaxNameLength()) + SPACE); 1830 } else if (attribute.equals(Setup.OWNER)) { 1831 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Owner(), 1832 InstanceManager.getDefault(CarOwners.class).getMaxNameLength()) + SPACE); 1833 } else if (attribute.equals(Setup.LENGTH)) { 1834 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Length(), 1835 InstanceManager.getDefault(CarLengths.class).getMaxNameLength()) + SPACE); 1836 } else if (attribute.equals(Setup.WEIGHT)) { 1837 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Weight(), 1838 Control.max_len_string_weight_name) + SPACE); 1839 } else if (attribute.equals(Setup.TRACK)) { 1840 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Track(), 1841 locationManager.getMaxTrackNameLength()) + SPACE); 1842 } else if (attribute.equals(Setup.LOCATION) && (isPickup || isLocal)) { 1843 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Location(), 1844 locationManager.getMaxTrackNameLength()) + SPACE); 1845 } else if (attribute.equals(Setup.LOCATION) && !isPickup) { 1846 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Location(), 1847 locationManager.getMaxLocationNameLength()) + SPACE); 1848 } else if (attribute.equals(Setup.DESTINATION) && !isPickup) { 1849 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Destination(), 1850 locationManager.getMaxTrackNameLength()) + SPACE); 1851 } else if (attribute.equals(Setup.DESTINATION) && isPickup) { 1852 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Destination(), 1853 locationManager.getMaxLocationNameLength()) + SPACE); 1854 } else if (attribute.equals(Setup.DEST_TRACK)) { 1855 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Dest_Track(), 1856 locationManager.getMaxLocationAndTrackNameLength() + 2) + SPACE); 1857 } else if (attribute.equals(Setup.FINAL_DEST)) { 1858 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Final_Dest(), 1859 locationManager.getMaxLocationNameLength()) + SPACE); 1860 } else if (attribute.equals(Setup.FINAL_DEST_TRACK)) { 1861 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Final_Dest_Track(), 1862 locationManager.getMaxLocationAndTrackNameLength() + 2) + SPACE); 1863 } else if (attribute.equals(Setup.HAZARDOUS)) { 1864 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Hazardous(), 1865 Setup.getHazardousMsg().length()) + SPACE); 1866 } else if (attribute.equals(Setup.RWE)) { 1867 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_RWE(), 1868 locationManager.getMaxLocationAndTrackNameLength()) + SPACE); 1869 } else if (attribute.equals(Setup.COMMENT)) { 1870 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Comment(), 1871 isEngine ? engineManager.getMaxCommentLength() : carManager.getMaxCommentLength()) + SPACE); 1872 } else if (attribute.equals(Setup.DROP_COMMENT)) { 1873 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Drop_Comment(), 1874 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()) + SPACE); 1875 } else if (attribute.equals(Setup.PICKUP_COMMENT)) { 1876 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Pickup_Comment(), 1877 InstanceManager.getDefault(CarLoads.class).getMaxLoadCommentLength()) + SPACE); 1878 } else if (attribute.equals(Setup.DIVISION)) { 1879 buf.append(padAndTruncateIfNeeded(TrainManifestHeaderText.getStringHeader_Division(), 1880 InstanceManager.getDefault(DivisionManager.class).getMaxDivisionNameLength()) + SPACE); 1881 } else if (attribute.equals(Setup.TAB)) { 1882 buf.append(createTabIfNeeded(Setup.getTab1Length())); 1883 } else if (attribute.equals(Setup.TAB2)) { 1884 buf.append(createTabIfNeeded(Setup.getTab2Length())); 1885 } else if (attribute.equals(Setup.TAB3)) { 1886 buf.append(createTabIfNeeded(Setup.getTab3Length())); 1887 } else { 1888 buf.append(attribute + SPACE); 1889 } 1890 } 1891 return buf.toString().trim(); 1892 } 1893 1894 protected void printTrackNameHeader(PrintWriter file, String trackName, boolean isManifest) { 1895 printHorizontalLine(file, isManifest); 1896 int lineLength = getLineLength(isManifest); 1897 String s = padAndTruncate(tabString(trackName.trim(), lineLength / 4 - trackName.trim().length() / 2), 1898 lineLength / 2) + 1899 VERTICAL_LINE_CHAR + 1900 tabString(trackName.trim(), lineLength / 4 - trackName.trim().length() / 2); 1901 s = padAndTruncate(s, lineLength); 1902 addLine(file, s); 1903 printHorizontalLine(file, isManifest); 1904 } 1905 1906 /** 1907 * Prints a line across the entire page. 1908 * 1909 * @param file The File to print to. 1910 * @param isManifest True if manifest, false if switch list. 1911 */ 1912 public void printHorizontalLine(PrintWriter file, boolean isManifest) { 1913 printHorizontalLine(file, 0, getLineLength(isManifest)); 1914 } 1915 1916 public void printHorizontalLine(PrintWriter file, int start, int end) { 1917 StringBuffer sb = new StringBuffer(); 1918 while (start-- > 0) { 1919 sb.append(SPACE); 1920 } 1921 while (end-- > 0) { 1922 sb.append(HORIZONTAL_LINE_CHAR); 1923 } 1924 addLine(file, sb.toString()); 1925 } 1926 1927 public static String getISO8601Date(boolean isModelYear) { 1928 Calendar calendar = Calendar.getInstance(); 1929 // use the JMRI Timebase (which may be a fast clock). 1930 calendar.setTime(jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime()); 1931 if (isModelYear && !Setup.getYearModeled().isEmpty()) { 1932 try { 1933 calendar.set(Calendar.YEAR, Integer.parseInt(Setup.getYearModeled().trim())); 1934 } catch (NumberFormatException e) { 1935 return Setup.getYearModeled(); 1936 } 1937 } 1938 return (new StdDateFormat()).format(calendar.getTime()); 1939 } 1940 1941 public static String getDate(Date date) { 1942 SimpleDateFormat format = new SimpleDateFormat("M/dd/yyyy HH:mm"); // NOI18N 1943 if (Setup.is12hrFormatEnabled()) { 1944 format = new SimpleDateFormat("M/dd/yyyy hh:mm a"); // NOI18N 1945 } 1946 return format.format(date); 1947 } 1948 1949 public static String getDate(boolean isModelYear) { 1950 Calendar calendar = Calendar.getInstance(); 1951 // use the JMRI Timebase (which may be a fast clock). 1952 calendar.setTime(jmri.InstanceManager.getDefault(jmri.Timebase.class).getTime()); 1953 if (isModelYear && !Setup.getYearModeled().equals(Setup.NONE)) { 1954 try { 1955 calendar.set(Calendar.YEAR, Integer.parseInt(Setup.getYearModeled().trim())); 1956 } catch (NumberFormatException e) { 1957 return Setup.getYearModeled(); 1958 } 1959 } 1960 return TrainCommon.getDate(calendar.getTime()); 1961 } 1962 1963 public static Date convertStringToDate(String date) { 1964 if (!date.isBlank()) { 1965 // create a date object from the string. 1966 try { 1967 // try MM/dd/yyyy HH:mm:ss. 1968 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); // NOI18N 1969 return formatter.parse(date); 1970 } catch (java.text.ParseException pe1) { 1971 // try the old 12 hour format (no seconds). 1972 try { 1973 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy hh:mmaa"); // NOI18N 1974 return formatter.parse(date); 1975 } catch (java.text.ParseException pe2) { 1976 try { 1977 // try 24hour clock. 1978 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm"); // NOI18N 1979 return formatter.parse(date); 1980 } catch (java.text.ParseException pe3) { 1981 log.debug("Not able to parse date: {}", date); 1982 } 1983 } 1984 } 1985 } 1986 return null; // there was no date specified. 1987 } 1988 1989 /** 1990 * Pads out a string by adding spaces to the end of the string, and will 1991 * remove characters from the end of the string if the string exceeds the 1992 * field size. 1993 * 1994 * @param s The string to pad. 1995 * @param fieldSize The maximum length of the string. 1996 * @return A String the specified length 1997 */ 1998 public static String padAndTruncateIfNeeded(String s, int fieldSize) { 1999 if (Setup.isTabEnabled()) { 2000 return padAndTruncate(s, fieldSize); 2001 } 2002 return s; 2003 } 2004 2005 public static String padAndTruncate(String s, int fieldSize) { 2006 s = padString(s, fieldSize); 2007 if (s.length() > fieldSize) { 2008 s = s.substring(0, fieldSize); 2009 } 2010 return s; 2011 } 2012 2013 /** 2014 * Adjusts string to be a certain number of characters by adding spaces to 2015 * the end of the string. 2016 * 2017 * @param s The string to pad 2018 * @param fieldSize The fixed length of the string. 2019 * @return A String the specified length 2020 */ 2021 public static String padString(String s, int fieldSize) { 2022 StringBuffer buf = new StringBuffer(s); 2023 while (buf.length() < fieldSize) { 2024 buf.append(SPACE); 2025 } 2026 return buf.toString(); 2027 } 2028 2029 /** 2030 * Creates a String of spaces to create a tab for text. Tabs must be 2031 * enabled. Setup.isTabEnabled() 2032 * 2033 * @param tabSize the length of tab 2034 * @return tab 2035 */ 2036 public static String createTabIfNeeded(int tabSize) { 2037 if (Setup.isTabEnabled()) { 2038 return tabString("", tabSize); 2039 } 2040 return ""; 2041 } 2042 2043 protected static String tabString(String s, int tabSize) { 2044 StringBuffer buf = new StringBuffer(); 2045 // TODO this doesn't consider the length of s string. 2046 while (buf.length() < tabSize) { 2047 buf.append(SPACE); 2048 } 2049 buf.append(s); 2050 return buf.toString(); 2051 } 2052 2053 /** 2054 * Returns the line length for manifest or switch list printout. Always an 2055 * even number. 2056 * 2057 * @param isManifest True if manifest. 2058 * @return line length for manifest or switch list. 2059 */ 2060 public static int getLineLength(boolean isManifest) { 2061 return getLineLength(isManifest ? Setup.getManifestOrientation() : Setup.getSwitchListOrientation(), 2062 Setup.getFontName(), Font.PLAIN, Setup.getManifestFontSize()); 2063 } 2064 2065 public static int getManifestHeaderLineLength() { 2066 return getLineLength(Setup.getManifestOrientation(), "SansSerif", Font.ITALIC, Setup.getManifestFontSize()); 2067 } 2068 2069 private static int getLineLength(String orientation, String fontName, int fontStyle, int fontSize) { 2070 Font font = new Font(fontName, fontStyle, fontSize); // NOI18N 2071 JLabel label = new JLabel(); 2072 FontMetrics metrics = label.getFontMetrics(font); 2073 int charwidth = metrics.charWidth('m'); 2074 if (charwidth == 0) { 2075 log.error("Line length charater width equal to zero. font size: {}, fontName: {}", fontSize, fontName); 2076 charwidth = fontSize / 2; // create a reasonable character width 2077 } 2078 // compute lines and columns within margins 2079 int charLength = getPageSize(orientation).width / charwidth; 2080 if (charLength % 2 != 0) { 2081 charLength--; // make it even 2082 } 2083 return charLength; 2084 } 2085 2086 private boolean checkStringLength(String string, boolean isManifest) { 2087 return checkStringLength(string, isManifest ? Setup.getManifestOrientation() : Setup.getSwitchListOrientation(), 2088 Setup.getFontName(), Setup.getManifestFontSize()); 2089 } 2090 2091 /** 2092 * Checks to see if the string fits on the page. 2093 * 2094 * @return false if string length is longer than page width. 2095 */ 2096 private boolean checkStringLength(String string, String orientation, String fontName, int fontSize) { 2097 // ignore text color controls when determining line length 2098 if (string.startsWith(TEXT_COLOR_START) && string.contains(TEXT_COLOR_DONE)) { 2099 string = string.substring(string.indexOf(TEXT_COLOR_DONE) + 2); 2100 } 2101 if (string.contains(TEXT_COLOR_END)) { 2102 string = string.substring(0, string.indexOf(TEXT_COLOR_END)); 2103 } 2104 Font font = new Font(fontName, Font.PLAIN, fontSize); // NOI18N 2105 JLabel label = new JLabel(); 2106 FontMetrics metrics = label.getFontMetrics(font); 2107 int stringWidth = metrics.stringWidth(string); 2108 return stringWidth <= getPageSize(orientation).width; 2109 } 2110 2111 protected static final Dimension PAPER_MARGINS = new Dimension(84, 72); 2112 2113 protected static Dimension getPageSize(String orientation) { 2114 // page size has been adjusted to account for margins of .5 2115 // Dimension(84, 72) 2116 Dimension pagesize = new Dimension(523, 720); // Portrait 8.5 x 11 2117 // landscape has .65 margins 2118 if (orientation.equals(Setup.LANDSCAPE)) { 2119 pagesize = new Dimension(702, 523); // 11 x 8.5 2120 } 2121 if (orientation.equals(Setup.HALFPAGE)) { 2122 pagesize = new Dimension(261, 720); // 4.25 x 11 2123 } 2124 if (orientation.equals(Setup.HANDHELD)) { 2125 pagesize = new Dimension(206, 720); // 3.25 x 11 2126 } 2127 return pagesize; 2128 } 2129 2130 /** 2131 * Produces a string using commas and spaces between the strings provided in 2132 * the array. Does not check for embedded commas in the string array. 2133 * 2134 * @param array The string array to be formated. 2135 * @return formated string using commas and spaces 2136 */ 2137 public static String formatStringToCommaSeparated(String[] array) { 2138 StringBuffer sbuf = new StringBuffer(""); 2139 for (String s : array) { 2140 if (s != null) { 2141 sbuf = sbuf.append(s + "," + SPACE); 2142 } 2143 } 2144 if (sbuf.length() > 2) { 2145 sbuf.setLength(sbuf.length() - 2); // remove trailing separators 2146 } 2147 return sbuf.toString(); 2148 } 2149 2150 /** 2151 * Adds HTML like color text control characters around a string. Note that 2152 * black is the standard text color, and if black is requested no control 2153 * characters are added. 2154 * 2155 * @param text the text to be modified 2156 * @param color the color the text is to be printed 2157 * @return formated text with color modifiers 2158 */ 2159 public static String formatColorString(String text, Color color) { 2160 String s = text; 2161 if (!color.equals(Color.black)) { 2162 s = TEXT_COLOR_START + ColorUtil.colorToColorName(color) + TEXT_COLOR_DONE + text + TEXT_COLOR_END; 2163 } 2164 return s; 2165 } 2166 2167 /** 2168 * Removes the color text control characters around the desired string 2169 * 2170 * @param string the string with control characters 2171 * @return pure text 2172 */ 2173 public static String getTextColorString(String string) { 2174 String text = string; 2175 if (string.contains(TEXT_COLOR_START)) { 2176 text = string.substring(0, string.indexOf(TEXT_COLOR_START)) + 2177 string.substring(string.indexOf(TEXT_COLOR_DONE) + 2); 2178 } 2179 if (text.contains(TEXT_COLOR_END)) { 2180 text = text.substring(0, text.indexOf(TEXT_COLOR_END)) + 2181 string.substring(string.indexOf(TEXT_COLOR_END) + TEXT_COLOR_END.length()); 2182 } 2183 return text; 2184 } 2185 2186 public static Color getTextColor(String string) { 2187 Color color = Color.black; 2188 if (string.contains(TEXT_COLOR_START)) { 2189 String c = string.substring(string.indexOf(TEXT_COLOR_START) + TEXT_COLOR_START.length()); 2190 c = c.substring(0, c.indexOf("\"")); 2191 color = ColorUtil.stringToColor(c); 2192 } 2193 return color; 2194 } 2195 2196 public static String getTextColorName(String string) { 2197 return ColorUtil.colorToColorName(getTextColor(string)); 2198 } 2199 2200 private static final Logger log = LoggerFactory.getLogger(TrainCommon.class); 2201}