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