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