001package jmri.jmrit.operations.router; 002 003import java.io.PrintWriter; 004import java.text.MessageFormat; 005import java.util.*; 006 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010import jmri.InstanceManager; 011import jmri.InstanceManagerAutoDefault; 012import jmri.jmrit.operations.locations.Location; 013import jmri.jmrit.operations.locations.Track; 014import jmri.jmrit.operations.rollingstock.RollingStock; 015import jmri.jmrit.operations.rollingstock.cars.Car; 016import jmri.jmrit.operations.setup.Setup; 017import jmri.jmrit.operations.trains.Train; 018import jmri.jmrit.operations.trains.TrainManager; 019import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 020 021/** 022 * Router for car movement. This code attempts to find a way (a route) to move a 023 * car to its final destination through the use of two or more trains. First the 024 * code tries to move car using a single train. If that fails, attempts are made 025 * using two trains via a classification/interchange (C/I) tracks, then yard 026 * tracks if enabled. Next attempts are made using three or more trains using 027 * any combination of C/I and yard tracks. If that fails and routing via staging 028 * is enabled, the code tries two trains using staging tracks, then multiple 029 * trains using a combination of C/I, yards, and staging tracks. Currently the 030 * router is limited to seven trains. 031 * 032 * @author Daniel Boudreau Copyright (C) 2010, 2011, 2012, 2013, 2015, 2021, 033 * 2022, 2024, 2026 034 */ 035public class Router extends TrainCommon implements InstanceManagerAutoDefault { 036 037 TrainManager trainManager = InstanceManager.getDefault(TrainManager.class); 038 039 protected final List<Track> _nextLocationTracks = new ArrayList<>(); 040 protected final List<Track> _lastLocationTracks = new ArrayList<>(); 041 private final List<Track> _otherLocationTracks = new ArrayList<>(); 042 043 protected final List<Track> _next2ndLocationTracks = new ArrayList<>(); 044 protected final List<Track> _next3rdLocationTracks = new ArrayList<>(); 045 protected final List<Track> _next4thLocationTracks = new ArrayList<>(); 046 047 protected final List<Train> _nextLocationTrains = new ArrayList<>(); 048 protected final List<Train> _lastLocationTrains = new ArrayList<>(); 049 protected List<Train> _excludeTrains; 050 051 protected Hashtable<String, Train> _listTrains = new Hashtable<>(); 052 053 protected static final String STATUS_NOT_THIS_TRAIN = Bundle.getMessage("RouterTrain"); 054 public static final String STATUS_NOT_THIS_TRAIN_PREFIX = 055 STATUS_NOT_THIS_TRAIN.substring(0, STATUS_NOT_THIS_TRAIN.indexOf('(')); 056 protected static final String STATUS_NOT_ABLE = Bundle.getMessage("RouterNotAble"); 057 protected static final String STATUS_ROUTER_DISABLED = Bundle.getMessage("RouterDisabled"); 058 059 private String _status = ""; 060 private Train _train = null; 061 PrintWriter _buildReport = null; // build report 062 Date _startTime; // when routing started 063 064 private static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED; 065 private boolean _addtoReport = false; 066 private boolean _addtoReportVeryDetailed = false; 067 068 /** 069 * Returns the status of the router when using the setDestination() for a 070 * car. 071 * 072 * @return Track.OKAY, STATUS_NOT_THIS_TRAIN, STATUS_NOT_ABLE, 073 * STATUS_ROUTER_DISABLED, or the destination track status is 074 * there's an issue. 075 */ 076 public String getStatus() { 077 return _status; 078 } 079 080 /** 081 * Determines if car can be routed to the destination track 082 * 083 * @param car the car being tested 084 * @param train the first train servicing the car, can be null 085 * @param track the destination track, can not be null 086 * @param buildReport the report, can be null 087 * @return true if the car can be routed to the track 088 */ 089 public boolean isCarRouteable(Car car, Train train, Track track, PrintWriter buildReport) { 090 addLine(buildReport, SEVEN, Bundle.getMessage("RouterIsCarRoutable", 091 car.toString(), car.getLocationName(), car.getTrackName(), car.getLoadName(), 092 track.getLocation().getName(), track.getName())); 093 return isCarRouteable(car, train, track.getLocation(), track, buildReport); 094 } 095 096 public boolean isCarRouteable(Car car, Train train, Location destination, Track track, PrintWriter buildReport) { 097 Car c = car.copy(); 098 c.setTrack(car.getTrack()); 099 c.setFinalDestination(destination); 100 c.setFinalDestinationTrack(track); 101 c.setScheduleItemId(car.getScheduleItemId()); 102 boolean results = setDestination(c, train, buildReport); 103 c.setDestination(null, null); // clear router car destinations 104 c.setFinalDestinationTrack(null); 105 // transfer route path info 106 car.setRoutePath(c.getRoutePath()); 107 return results; 108 } 109 110 /** 111 * Attempts to set the car's destination if a final destination exists. Only 112 * sets the car's destination if the train is part of the car's route. 113 * 114 * @param car the car to route 115 * @param train the first train to carry this car, can be null 116 * @param buildReport PrintWriter for build report, and can be null 117 * @return true if car can be routed. 118 */ 119 public boolean setDestination(Car car, Train train, PrintWriter buildReport) { 120 if (car.getTrack() == null || car.getFinalDestination() == null) { 121 return false; 122 } 123 _startTime = new Date(); 124 _status = Track.OKAY; 125 _train = train; 126 _buildReport = buildReport; 127 _addtoReport = Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED) || 128 Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED); 129 _addtoReportVeryDetailed = Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED); 130 log.debug("Car ({}) at location ({}, {}) final destination ({}, {}) car routing begins", car, 131 car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(), 132 car.getFinalDestinationTrackName()); 133 if (_train != null) { 134 log.debug("Routing using train ({})", train.getName()); 135 } 136 // is car part of kernel? 137 if (car.getKernel() != null && !car.isLead()) { 138 return false; 139 } 140 // note clone car has the car's "final destination" as its destination 141 Car clone = clone(car); 142 // Note the following test doesn't check for car length which is what we 143 // want. 144 // Also ignores spur schedule since the car's destination is already 145 // set. 146 _status = clone.checkDestination(clone.getDestination(), clone.getDestinationTrack()); 147 if (!_status.equals(Track.OKAY)) { 148 addLine(Bundle.getMessage("RouterCanNotDeliverCar", 149 car.toString(), car.getFinalDestinationName(), car.getFinalDestinationTrackName(), 150 _status, (car.getFinalDestinationTrack() == null ? Bundle.getMessage("RouterDestination") 151 : car.getFinalDestinationTrack().getTrackTypeName()))); 152 return false; 153 } 154 // check to see if car has a destination track or one is available 155 if (!checkForDestinationTrack(clone)) { 156 return false; // no destination track found 157 } 158 // check to see if car will move to destination using a single train 159 if (checkForSingleTrain(car, clone)) { 160 return true; // a single train can service this car 161 } 162 if (!Setup.isCarRoutingEnabled()) { 163 log.debug("Car ({}) final destination ({}) is not served directly by any train", car, 164 car.getFinalDestinationName()); // NOI18N 165 _status = STATUS_ROUTER_DISABLED; 166 car.setFinalDestination(null); 167 car.setFinalDestinationTrack(null); 168 return false; 169 } 170 log.debug("Car ({}) final destination ({}) is not served by a single train", car, 171 car.getFinalDestinationName()); 172 // was the request for a local move? Try multiple trains to move car 173 if (car.getLocationName().equals(car.getFinalDestinationName())) { 174 addLine(Bundle.getMessage("RouterCouldNotFindTrain", 175 car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(), 176 car.getFinalDestinationTrackName())); 177 } 178 if (_addtoReport) { 179 addLine(Bundle.getMessage("RouterBeginTwoTrain", 180 car.toString(), car.getLocationName(), car.getFinalDestinationName())); 181 } 182 183 setupLists(); 184 excludeTrains(car); 185 excludeTracks(); 186 187 // first try using 2 trains and an interchange track to route the car 188 if (setCarDestinationTwoTrainsInterchange(car)) { 189 if (car.getDestination() == null) { 190 log.debug( 191 "Was able to find a route via classification/interchange track, but not using specified train" + 192 " or car destination not set, try again using yard tracks"); // NOI18N 193 if (setCarDestinationTwoTrainsYard(car)) { 194 log.debug("Was able to find route via yard ({}, {}) for car ({})", car.getDestinationName(), 195 car.getDestinationTrackName(), car); 196 } 197 } else { 198 log.debug("Was able to find route via interchange ({}, {}) for car ({})", car.getDestinationName(), 199 car.getDestinationTrackName(), car); 200 } 201 if (_addtoReportVeryDetailed) { 202 addLine(Bundle.getMessage("RouterTwoTrainsSuccess", car.toString())); 203 } 204 // now try 2 trains using a yard track 205 } else if (setCarDestinationTwoTrainsYard(car)) { 206 log.debug("Was able to find route via yard ({}, {}) for car ({}) using two trains", 207 car.getDestinationName(), car.getDestinationTrackName(), car); 208 if (_addtoReportVeryDetailed) { 209 addLine(Bundle.getMessage("RouterTwoTrainsSuccess", car.toString())); 210 } 211 // now try 3 or more trains to route car, but not through staging 212 } else if (setCarDestinationMultipleTrains(car, false)) { 213 log.debug("Was able to find multiple train route for car ({})", car); 214 // now try 2 trains using a staging track to connect 215 } else if (setCarDestinationTwoTrainsStaging(car)) { 216 log.debug("Was able to find route via staging ({}, {}) for car ({}) using two trains", 217 car.getDestinationName(), car.getDestinationTrackName(), car); 218 // now try 3 or more trains to route car, include staging if enabled 219 } else if (setCarDestinationMultipleTrains(car, true)) { 220 log.debug("Was able to find multiple train route for car ({}) through staging", car); 221 } else { 222 log.debug("Wasn't able to set route for car ({}) took {} mSec", car, 223 new Date().getTime() - _startTime.getTime()); 224 _status = STATUS_NOT_ABLE; 225 return false; // maybe next time 226 } 227 return true; // car's destination has been set 228 } 229 230 /* 231 * Checks to see if the car has a destination track, no destination track, 232 * searches for one. returns true if the car has a destination track or if 233 * there's one available. 234 */ 235 private boolean checkForDestinationTrack(Car clone) { 236 if (clone.getDestination() != null && clone.getDestinationTrack() == null) { 237 // determine if there's a track that can service the car 238 String status = ""; 239 for (Track track : clone.getDestination().getTracksList()) { 240 status = track.isRollingStockAccepted(clone); 241 if (status.equals(Track.OKAY) || status.startsWith(Track.LENGTH)) { 242 log.debug("Track ({}) will accept car ({})", track.getName(), clone.toString()); 243 break; 244 } 245 } 246 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 247 addLine(_status = Bundle.getMessage("RouterNoTracks", 248 clone.getDestinationName(), clone.toString())); 249 return false; 250 } 251 } 252 return true; 253 } 254 255 /** 256 * Checks to see if a single train can transport car to its final 257 * destination. Special case if car is departing staging. 258 * 259 * @return true if single train can transport car to its final destination. 260 */ 261 private boolean checkForSingleTrain(Car car, Car clone) { 262 boolean trainServicesCar = false; // true the specified train can service the car 263 Train testTrain = null; 264 if (_train != null) { 265 trainServicesCar = _train.isServiceable(_buildReport, clone); 266 } 267 if (trainServicesCar) { 268 testTrain = _train; // use the specified train 269 log.debug("Train ({}) can service car ({})", _train.getName(), car.toString()); 270 } else if (_train != null && !_train.getServiceStatus().equals(Train.NONE)) { 271 // _train isn't able to service car 272 // determine if car was attempting to go to the train's termination staging 273 String trackName = car.getFinalDestinationTrackName(); 274 if (car.getFinalDestinationTrack() == null && 275 car.getFinalDestinationName().equals(_train.getTrainTerminatesName()) && 276 _train.getTerminationTrack() != null) { 277 trackName = _train.getTerminationTrack().getName(); // use staging track 278 } 279 // report that train can't service car 280 addLine(Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(), 281 car.getFinalDestinationName(), trackName, _train.getServiceStatus())); 282 if (!car.getTrack().isStaging() && 283 !_train.isServiceAllCarsWithFinalDestinationsEnabled()) { 284 _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()}); 285 return true; // temporary issue with train moves, length, or destination track length 286 } 287 } 288 // Determines if specified train can service car out of staging. 289 // Note that the router code will try to route the car using 290 // two or more trains just to get the car out of staging. 291 if (car.getTrack().isStaging() && _train != null && !trainServicesCar) { 292 addLine(Bundle.getMessage("RouterTrainCanNotStaging", 293 _train.getName(), car.toString(), car.getLocationName(), 294 clone.getDestinationName(), clone.getDestinationTrackName())); 295 if (!_train.getServiceStatus().equals(Train.NONE)) { 296 addLine(_train.getServiceStatus()); 297 } 298 addLine(Bundle.getMessage("RouterStagingTryRouting", car.toString(), clone.getLocationName(), 299 clone.getDestinationName(), clone.getDestinationTrackName())); 300 // note that testTrain = null, return false 301 } else if (!trainServicesCar) { 302 List<Train> excludeTrains = new ArrayList<>(Arrays.asList(_train)); 303 testTrain = trainManager.getTrainForCar(clone, excludeTrains, _buildReport, true); 304 } 305 // report that another train could transport the car 306 if (testTrain != null && 307 _train != null && 308 !trainServicesCar && 309 _train.isServiceAllCarsWithFinalDestinationsEnabled()) { 310 // log.debug("Option to service all cars with a final destination is enabled"); 311 addLine(Bundle.getMessage("RouterOptionToCarry", 312 _train.getName(), testTrain.getName(), car.toString(), 313 clone.getDestinationName(), clone.getDestinationTrackName())); 314 testTrain = null; // return false 315 } 316 if (testTrain != null) { 317 return finishRouteUsingOneTrain(testTrain, car, clone); 318 } 319 return false; 320 } 321 322 /** 323 * A single train can service the car. Provide various messages to build 324 * report detailing which train can service the car. Also checks to see if 325 * the needs to go the alternate track or yard track if the car's final 326 * destination track is full. Returns false if car is stuck in staging. Sets 327 * the car's destination if specified _train is available 328 * 329 * @return true for all cases except if car is departing staging and is 330 * stuck there. 331 */ 332 private boolean finishRouteUsingOneTrain(Train testTrain, Car car, Car clone) { 333 addLine(Bundle.getMessage("RouterTrainCanTransport", testTrain.getName(), car.toString(), 334 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName(), 335 clone.getDestinationName(), clone.getDestinationTrackName())); 336 showRoute(car, new ArrayList<>(Arrays.asList(testTrain)), 337 new ArrayList<>(Arrays.asList(car.getFinalDestinationTrack()))); 338 // don't modify car if a train wasn't specified 339 if (_train == null) { 340 return true; // done, car can be routed 341 } 342 // now check to see if specified train can service car directly 343 else if (_train != testTrain) { 344 addLine(Bundle.getMessage("TrainDoesNotServiceCar", _train.getName(), car.toString(), 345 clone.getDestinationName(), clone.getDestinationTrackName())); 346 _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{testTrain.getName()}); 347 return true; // car can be routed, but not by this train! 348 } 349 _status = car.setDestination(clone.getDestination(), clone.getDestinationTrack()); 350 if (_status.equals(Track.OKAY)) { 351 return true; // done, car has new destination 352 } 353 addLine(Bundle.getMessage("RouterCanNotDeliverCar", car.toString(), clone.getDestinationName(), 354 clone.getDestinationTrackName(), _status, 355 (clone.getDestinationTrack() == null ? Bundle.getMessage("RouterDestination") 356 : clone.getDestinationTrack().getTrackTypeName()))); 357 // check to see if an alternative track was specified 358 if ((_status.startsWith(Track.LENGTH) || _status.startsWith(Track.SCHEDULE)) && 359 clone.getDestinationTrack() != null && 360 clone.getDestinationTrack().getAlternateTrack() != null && 361 clone.getDestinationTrack().getAlternateTrack() != car.getTrack()) { 362 String status = car.setDestination(clone.getDestination(), clone.getDestinationTrack().getAlternateTrack()); 363 if (status.equals(Track.OKAY)) { 364 if (_train.isServiceable(car)) { 365 addLine(Bundle.getMessage("RouterSendCarToAlternative", 366 car.toString(), clone.getDestinationTrack().getAlternateTrack().getName(), 367 clone.getDestination().getName())); 368 return true; // car is going to alternate track 369 } 370 addLine(Bundle.getMessage("RouterNotSendCarToAlternative", _train.getName(), car.toString(), 371 clone.getDestinationTrack().getAlternateTrack().getName(), 372 clone.getDestination().getName())); 373 } else { 374 addLine(Bundle.getMessage("RouterAlternateFailed", 375 clone.getDestinationTrack().getAlternateTrack().getName(), status)); 376 } 377 } else if (clone.getDestinationTrack() != null && 378 clone.getDestinationTrack().getAlternateTrack() != null && 379 clone.getDestinationTrack().getAlternateTrack() == car.getTrack()) { 380 // state that car is spotted at the alternative track 381 addLine(Bundle.getMessage("RouterAtAlternate", 382 car.toString(), clone.getDestinationTrack().getAlternateTrack().getName(), 383 clone.getLocationName(), clone.getDestinationTrackName())); 384 } else if (car.getLocation() == clone.getDestination()) { 385 // state that alternative and yard track options are not available 386 // if car is at final destination 387 addLine(Bundle.getMessage("RouterIgnoreAlternate", car.toString(), car.getLocationName())); 388 } 389 // check to see if spur was full, if so, forward to yard if possible 390 if (Setup.isForwardToYardEnabled() && 391 _status.startsWith(Track.LENGTH) && 392 car.getLocation() != clone.getDestination()) { 393 addLine(Bundle.getMessage("RouterSpurFull", 394 clone.getDestinationName(), clone.getDestinationTrackName(), clone.getDestinationName())); 395 Location dest = clone.getDestination(); 396 List<Track> yards = dest.getTracksByMoves(Track.YARD); 397 log.debug("Found {} yard(s) at destination ({})", yards.size(), clone.getDestinationName()); 398 for (Track track : yards) { 399 String status = car.setDestination(dest, track); 400 if (status.equals(Track.OKAY)) { 401 if (!_train.isServiceable(car)) { 402 log.debug("Train ({}) can not deliver car ({}) to yard ({})", _train.getName(), car, 403 track.getName()); 404 continue; 405 } 406 addLine(Bundle.getMessage("RouterSendCarToYard", 407 car.toString(), dest.getName(), track.getName(), dest.getName())); 408 return true; // car is going to a yard 409 } else { 410 addLine(Bundle.getMessage("RouterCanNotUseYard", 411 track.getLocation().getName(), track.getName(), status)); 412 } 413 } 414 addLine(Bundle.getMessage("RouterNoYardTracks", 415 dest.getName(), car.toString())); 416 } 417 car.setDestination(null, null); 418 if (car.getTrack().isStaging()) { 419 addLine(Bundle.getMessage("RouterStagingTryRouting", car.toString(), clone.getLocationName(), 420 clone.getDestinationName(), clone.getDestinationTrackName())); 421 return false; // try 2 or more trains 422 } 423 return true; // able to route, but unable to set the car's destination 424 } 425 426 private void setupLists() { 427 _nextLocationTracks.clear(); 428 _next2ndLocationTracks.clear(); 429 _next3rdLocationTracks.clear(); 430 _next4thLocationTracks.clear(); 431 _lastLocationTracks.clear(); 432 _otherLocationTracks.clear(); 433 _nextLocationTrains.clear(); 434 _lastLocationTrains.clear(); 435 _listTrains.clear(); 436 } 437 438 private void excludeTrains(Car car) { 439 if (_addtoReportVeryDetailed) { 440 addLine(BLANK_LINE); 441 addLine(Bundle.getMessage("RouterExcludeTrains", car.toString(), 442 car.getTypeName(), car.getLoadType().toLowerCase(), car.getLoadName(), car.getRoadName(), 443 car.getBuilt(), car.getOwnerName())); 444 } 445 _excludeTrains = trainManager.getExcludeTrainListForCar(car, _buildReport); 446 } 447 448 /* 449 * No routing through alternate tracks. List them. 450 */ 451 private void excludeTracks() { 452 if (_addtoReportVeryDetailed) { 453 addLine(BLANK_LINE); 454 addLine(Bundle.getMessage("RouterExcludeAltTracks")); 455 List<Track> tracks = locationManager.getTracks(null); 456 for (Track track : tracks) { 457 if (track.isAlternate() && 458 (track.getTrackType().equals(Track.INTERCHANGE) || 459 track.getTrackType().equals(Track.YARD) && Setup.isCarRoutingViaYardsEnabled())) { 460 addLine(Bundle.getMessage("RouterExcludeAltTrack", track.getTrackTypeName(), 461 track.getLocation().getName(), track.getName())); 462 } 463 } 464 } 465 } 466 467 /** 468 * Sets a car's destination to an interchange track if two trains can route 469 * the car. 470 * 471 * @param car the car to be routed 472 * @return true if car's destination has been modified to an interchange. 473 * False if an interchange track wasn't found that could service the 474 * car's final destination. 475 */ 476 private boolean setCarDestinationTwoTrainsInterchange(Car car) { 477 return setCarDestinationTwoTrains(car, Track.INTERCHANGE); 478 } 479 480 /** 481 * Sets a car's destination to a yard track if two trains can route the car. 482 * 483 * @param car the car to be routed 484 * @return true if car's destination has been modified to a yard. False if a 485 * yard track wasn't found that could service the car's final 486 * destination. 487 */ 488 private boolean setCarDestinationTwoTrainsYard(Car car) { 489 if (Setup.isCarRoutingViaYardsEnabled()) { 490 return setCarDestinationTwoTrains(car, Track.YARD); 491 } 492 return false; 493 } 494 495 /** 496 * Sets a car's destination to a staging track if two trains can route the 497 * car. 498 * 499 * @param car the car to be routed 500 * @return true if car's destination has been modified to a staging track. 501 * False if a staging track wasn't found that could service the 502 * car's final destination. 503 */ 504 private boolean setCarDestinationTwoTrainsStaging(Car car) { 505 if (Setup.isCarRoutingViaStagingEnabled()) { 506 addLine(BLANK_LINE); 507 addLine(Bundle.getMessage("RouterAttemptStaging", car.toString(), 508 car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 509 return setCarDestinationTwoTrains(car, Track.STAGING); 510 } 511 return false; 512 } 513 514 /* 515 * Note that this routine loads the last set of tracks and trains that can 516 * service the car to its final location. This routine attempts to find a 517 * "two" train route by cycling through various interchange, yard, and 518 * staging tracks searching for a second train that can pull the car from 519 * the track and deliver the car to the its destination. Then the program 520 * determines if the train being built or another train (first) can deliver 521 * the car to the track from its current location. If successful, a two 522 * train route was found, and returns true. 523 */ 524 private boolean setCarDestinationTwoTrains(Car car, String trackType) { 525 Car testCar = clone(car); // reload 526 log.debug("Two train routing, find {} track for car ({}) final destination ({}, {})", trackType, car, 527 testCar.getDestinationName(), testCar.getDestinationTrackName()); 528 if (_addtoReportVeryDetailed) { 529 addLine(BLANK_LINE); 530 addLine(Bundle.getMessage("RouterFindTrack", Track.getTrackTypeName(trackType), car.toString(), 531 testCar.getDestinationName(), testCar.getDestinationTrackName())); 532 } 533 boolean foundRoute = false; 534 // now search for a yard or interchange that a train can pick up and 535 // deliver the car to its destination 536 List<Track> tracks = getTracks(car, testCar, trackType); 537 for (Track track : tracks) { 538 if (_addtoReportVeryDetailed) { 539 addLine(BLANK_LINE); 540 addLine(Bundle.getMessage("RouterFoundTrack", 541 Track.getTrackTypeName(trackType), track.getLocation().getName(), 542 track.getName(), car.toString())); 543 } 544 // test to see if there's a train that can deliver the car to its 545 // final location 546 testCar.setTrack(track); 547 testCar.setDestination(car.getFinalDestination()); 548 // note that destination track can be null 549 testCar.setDestinationTrack(car.getFinalDestinationTrack()); 550 Train secondTrain = trainManager.getTrainForCar(testCar, _excludeTrains, _buildReport, false); 551 if (secondTrain == null) { 552 // maybe the train being built can service the car? 553 String specified = canSpecifiedTrainService(testCar); 554 if (specified.equals(NOT_NOW)) { 555 secondTrain = _train; 556 } else { 557 if (_addtoReportVeryDetailed) { 558 addLine(Bundle.getMessage("RouterNotFindTrain", testCar.toString(), 559 Track.getTrackTypeName(trackType), track.getLocation().getName(), track.getName(), 560 testCar.getDestinationName(), testCar.getDestinationTrackName())); 561 } 562 continue; 563 } 564 } 565 if (_addtoReportVeryDetailed) { 566 addLine(Bundle.getMessage("RouterTrainCanTransport", 567 secondTrain.getName(), car.toString(), testCar.getTrack().getTrackTypeName(), 568 testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(), 569 testCar.getDestinationTrackName())); 570 } 571 // Save the "last" tracks for later use if needed 572 _lastLocationTracks.add(track); 573 _lastLocationTrains.add(secondTrain); 574 // now try to forward car to this track 575 testCar.setTrack(car.getTrack()); // restore car origin 576 testCar.setDestination(track.getLocation()); 577 testCar.setDestinationTrack(track); 578 // determine if car can be transported from current location to this 579 // interchange, yard, or staging track 580 // Now find a train that will transport the car to this track 581 Train firstTrain = null; 582 String specified = canSpecifiedTrainService(testCar); 583 if (specified.equals(YES)) { 584 firstTrain = _train; 585 } else if (specified.equals(NOT_NOW)) { 586 // found a two train route for this car, show the car's route 587 List<Train> trains = new ArrayList<>(Arrays.asList(_train, secondTrain)); 588 tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack())); 589 showRoute(car, trains, tracks); 590 591 addLine(Bundle.getMessage("RouterTrainCanNotDueTo", 592 _train.getName(), car.toString(), track.getLocation().getName(), track.getName(), 593 _train.getServiceStatus())); 594 foundRoute = true; // issue is route moves or train length 595 } else { 596 firstTrain = trainManager.getTrainForCar(testCar, _excludeTrains, _buildReport, false); 597 } 598 // check to see if a train or trains with the same route is delivering and pulling the car to an interchange track 599 if (firstTrain != null && 600 firstTrain.getRoute() == secondTrain.getRoute() && 601 track.isInterchange() && 602 track.getPickupOption().equals(Track.ANY)) { 603 if (_addtoReportVeryDetailed) { 604 addLine(Bundle.getMessage("RouterSameInterchange", firstTrain.getName(), 605 track.getLocation().getName(), track.getName())); 606 } 607 List<Train> excludeTrains = new ArrayList<>(Arrays.asList(firstTrain)); 608 firstTrain = trainManager.getTrainForCar(testCar, excludeTrains, _buildReport, true); 609 } 610 if (firstTrain == null && _addtoReportVeryDetailed) { 611 addLine(Bundle.getMessage("RouterNotFindTrain", testCar.toString(), 612 testCar.getTrack().getTrackTypeName(), testCar.getTrack().getLocation().getName(), 613 testCar.getTrack().getName(), testCar.getDestinationName(), testCar.getDestinationTrackName())); 614 } 615 // Can the specified train carry this car out of staging? 616 if (_train != null && car.getTrack().isStaging() && !specified.equals(YES)) { 617 if (_addtoReport) { 618 addLine(Bundle.getMessage("RouterTrainCanNot", 619 _train.getName(), car.toString(), car.getLocationName(), 620 car.getTrackName(), track.getLocation().getName(), track.getName())); 621 } 622 continue; // can't use this train 623 } 624 // Is the option for the specified train carry this car? 625 if (firstTrain != null && 626 _train != null && 627 _train.isServiceAllCarsWithFinalDestinationsEnabled() && 628 !specified.equals(YES)) { 629 if (_addtoReport) { 630 addLine(Bundle.getMessage("RouterOptionToCarry", 631 _train.getName(), firstTrain.getName(), car.toString(), 632 track.getLocation().getName(), track.getName())); 633 } 634 continue; // can't use this train 635 } 636 if (firstTrain != null) { 637 foundRoute = true; // found a route 638 if (_addtoReportVeryDetailed) { 639 addLine(Bundle.getMessage("RouterTrainCanTransport", firstTrain.getName(), car.toString(), 640 testCar.getTrack().getTrackTypeName(), 641 testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(), 642 testCar.getDestinationTrackName())); 643 } 644 // found a two train route for this car, show the car's route 645 List<Train> trains = new ArrayList<>(Arrays.asList(firstTrain, secondTrain)); 646 tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack())); 647 showRoute(car, trains, tracks); 648 649 _status = car.checkDestination(track.getLocation(), track); 650 if (_status.startsWith(Track.LENGTH)) { 651 // if the issue is length at the interim track, add message 652 // to build report 653 addLine(Bundle.getMessage("RouterCanNotDeliverCar", 654 car.toString(), track.getLocation().getName(), track.getName(), 655 _status, track.getTrackTypeName())); 656 continue; 657 } 658 if (_status.equals(Track.OKAY)) { 659 // only set car's destination if specified train can service 660 // car 661 if (_train != null && _train != firstTrain) { 662 addLine(Bundle.getMessage("TrainDoesNotServiceCar", 663 _train.getName(), car.toString(), testCar.getDestinationName(), 664 testCar.getDestinationTrackName())); 665 _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{firstTrain.getName()}); 666 continue;// found a route but it doesn't start with the 667 // specified train 668 } 669 // is this the staging track assigned to the specified 670 // train? 671 if (track.isStaging() && 672 firstTrain.getTerminationTrack() != null && 673 firstTrain.getTerminationTrack() != track) { 674 addLine(Bundle.getMessage("RouterTrainIntoStaging", firstTrain.getName(), 675 firstTrain.getTerminationTrack().getLocation().getName(), 676 firstTrain.getTerminationTrack().getName())); 677 continue; 678 } 679 _status = car.setDestination(track.getLocation(), track); 680 if (_addtoReport) { 681 addLine(Bundle.getMessage("RouterTrainCanService", 682 firstTrain.getName(), car.toString(), car.getLocationName(), car.getTrackName(), 683 Track.getTrackTypeName(trackType), track.getLocation().getName(), track.getName())); 684 } 685 return true; // the specified train and another train can 686 // carry the car to its destination 687 } 688 } 689 } 690 if (foundRoute) { 691 if (_train != null) { 692 _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()}); 693 } else { 694 _status = STATUS_NOT_ABLE; 695 } 696 } 697 return foundRoute; 698 } 699 700 /** 701 * This routine builds a set of tracks that could be used for routing. It 702 * also lists all of the tracks that can't be used. 703 * 704 * @param car The car being routed 705 * @param testCar the test car 706 * @param trackType the type of track used for routing 707 * @return list of usable tracks 708 */ 709 private List<Track> getTracks(Car car, Car testCar, String trackType) { 710 List<Track> inTracks = locationManager.getTracksByMoves(trackType); 711 List<Track> tracks = new ArrayList<Track>(); 712 for (Track track : inTracks) { 713 if (car.getTrack() == track || car.getFinalDestinationTrack() == track) { 714 continue; // don't use car's current track 715 } 716 // can't use staging if car's load can be modified 717 if (trackType.equals(Track.STAGING) && track.isModifyLoadsEnabled()) { 718 if (_addtoReportVeryDetailed) { 719 addLine(Bundle.getMessage("RouterStagingExcluded", 720 track.getLocation().getName(), track.getName())); 721 } 722 continue; 723 } 724 String status = track.isRollingStockAccepted(testCar); 725 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 726 if (_addtoReportVeryDetailed) { 727 addLine(Bundle.getMessage("RouterCanNotDeliverCar", 728 car.toString(), track.getLocation().getName(), track.getName(), 729 status, track.getTrackTypeName())); 730 } 731 continue; 732 } 733 tracks.add(track); 734 } 735 return tracks; 736 } 737 738 /* 739 * Note that "last" set of location/tracks (_lastLocationTracks) was loaded 740 * by setCarDestinationTwoTrains. The following code builds two additional 741 * sets of location/tracks called "next" (_nextLocationTracks) and "other" 742 * (_otherLocationTracks). "next" is the next set of location/tracks that 743 * the car can reach by a single train. "last" is the last set of 744 * location/tracks that services the cars final destination. And "other" is 745 * the remaining sets of location/tracks that are not "next" or "last". The 746 * code then tries to connect the "next" and "last" location/track sets with 747 * a train that can service the car. If successful, that would be a three 748 * train route for the car. If not successful, the code than tries 749 * combinations of "next", "other" and "last" location/tracks to create a 750 * route for the car. 751 */ 752 private boolean setCarDestinationMultipleTrains(Car car, boolean useStaging) { 753 if (useStaging && !Setup.isCarRoutingViaStagingEnabled()) 754 return false; // routing via staging is disabled 755 756 if (_addtoReportVeryDetailed) { 757 addLine(BLANK_LINE); 758 } 759 if (_lastLocationTracks.isEmpty()) { 760 if (useStaging) { 761 addLine(Bundle.getMessage("RouterCouldNotFindStaging", 762 car.getFinalDestinationName())); 763 } else { 764 addLine(Bundle.getMessage("RouterCouldNotFindLast", 765 car.getFinalDestinationName())); 766 } 767 return false; 768 } 769 770 Car testCar = clone(car); // reload 771 // build the "next" and "other" location/tracks 772 if (_nextLocationTracks.isEmpty() && _otherLocationTracks.isEmpty()) { 773 loadInterchangeAndYards(car, testCar); 774 } 775 // add staging if requested 776 if (useStaging) { 777 loadStaging(car, testCar); 778 } 779 780 if (_nextLocationTracks.isEmpty()) { 781 addLine(Bundle.getMessage("RouterCouldNotFindLoc", 782 car.getLocationName())); 783 return false; 784 } 785 786 addLine(Bundle.getMessage("RouterTwoTrainsFailed", car)); 787 788 if (_addtoReport) { 789 // tracks that could be the very next destination for the car 790 for (Track t : _nextLocationTracks) { 791 addLine(Bundle.getMessage("RouterNextTrack", t.getTrackTypeName(), t.getLocation().getName(), 792 t.getName(), car, car.getLocationName(), car.getTrackName(), 793 _nextLocationTrains.get(_nextLocationTracks.indexOf(t)))); 794 } 795 // tracks that could be the next to last destination for the car 796 for (Track t : _lastLocationTracks) { 797 addLine(Bundle.getMessage("RouterLastTrack", 798 t.getTrackTypeName(), t.getLocation().getName(), t.getName(), car, 799 car.getFinalDestinationName(), car.getFinalDestinationTrackName(), 800 _lastLocationTrains.get(_lastLocationTracks.indexOf(t)))); 801 } 802 } 803 if (_addtoReportVeryDetailed) { 804 // tracks that are not the next or the last list 805 for (Track t : _otherLocationTracks) { 806 addLine(Bundle.getMessage("RouterOtherTrack", t.getTrackTypeName(), t.getLocation().getName(), 807 t.getName(), car)); 808 } 809 addLine(BLANK_LINE); 810 } 811 boolean foundRoute = routeUsing3Trains(car); 812 if (!foundRoute) { 813 log.debug("Using 3 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName()); 814 foundRoute = routeUsing4Trains(car); 815 } 816 if (!foundRoute) { 817 log.debug("Using 4 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName()); 818 foundRoute = routeUsing5Trains(car); 819 } 820 if (!foundRoute) { 821 log.debug("Using 5 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName()); 822 foundRoute = routeUsing6Trains(car); 823 } 824 if (!foundRoute) { 825 log.debug("Using 6 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName()); 826 foundRoute = routeUsing7Trains(car); 827 } 828 if (!foundRoute) { 829 addLine(Bundle.getMessage("RouterNotAbleToRoute", car.toString(), car.getLocationName(), 830 car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName())); 831 } 832 return foundRoute; 833 } 834 835 private void loadInterchangeAndYards(Car car, Car testCar) { 836 List<Track> tracks; 837 // start with interchanges 838 tracks = locationManager.getTracksByMoves(Track.INTERCHANGE); 839 loadTracksAndTrains(car, testCar, tracks); 840 // next load yards if enabled 841 if (Setup.isCarRoutingViaYardsEnabled()) { 842 tracks = locationManager.getTracksByMoves(Track.YARD); 843 loadTracksAndTrains(car, testCar, tracks); 844 } 845 } 846 847 private void loadStaging(Car car, Car testCar) { 848 // add staging if requested 849 List<Track> stagingTracks = locationManager.getTracksByMoves(Track.STAGING); 850 List<Track> tracks = new ArrayList<Track>(); 851 for (Track staging : stagingTracks) { 852 if (!staging.isModifyLoadsEnabled()) { 853 tracks.add(staging); 854 } 855 } 856 loadTracksAndTrains(car, testCar, tracks); 857 } 858 859 private boolean routeUsing3Trains(Car car) { 860 addLine(Bundle.getMessage("RouterNTrains", "3", car.getFinalDestinationName(), 861 car.getFinalDestinationTrackName())); 862 Car testCar = clone(car); // reload 863 boolean foundRoute = false; 864 for (Track nlt : _nextLocationTracks) { 865 for (Track llt : _lastLocationTracks) { 866 // does a train service these two locations? 867 Train middleTrain = 868 getTrainForCar(testCar, nlt, llt, _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), 869 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))); 870 if (middleTrain != null) { 871 log.debug("Found 3 train route, setting car destination ({}, {})", nlt.getLocation().getName(), 872 nlt.getName()); 873 foundRoute = true; 874 // show the car's route by building an ordered list of 875 // trains and tracks 876 List<Train> trains = new ArrayList<>( 877 Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain, 878 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)))); 879 List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, llt, car.getFinalDestinationTrack())); 880 showRoute(car, trains, tracks); 881 if (finshSettingRouteFor(car, nlt)) { 882 return true; // done 3 train routing 883 } 884 break; // there was an issue with the first stop in the 885 // route 886 } 887 } 888 } 889 return foundRoute; 890 } 891 892 private boolean routeUsing4Trains(Car car) { 893 addLine(Bundle.getMessage("RouterNTrains", "4", car.getFinalDestinationName(), 894 car.getFinalDestinationTrackName())); 895 Car testCar = clone(car); // reload 896 boolean foundRoute = false; 897 for (Track nlt : _nextLocationTracks) { 898 otherloop: for (Track mlt : _otherLocationTracks) { 899 Train middleTrain2 = getTrainForCar(testCar, nlt, mlt, 900 _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null); 901 if (middleTrain2 == null) { 902 continue; 903 } 904 // build a list of tracks that are reachable from the 1st 905 // interchange 906 if (!_next2ndLocationTracks.contains(mlt)) { 907 _next2ndLocationTracks.add(mlt); 908 if (_addtoReport) { 909 addLine(Bundle.getMessage("RouterNextHop", mlt.getTrackTypeName(), mlt.getLocation().getName(), 910 mlt.getName(), car, nlt.getLocation().getName(), nlt.getName(), 911 middleTrain2.getName())); 912 } 913 } 914 for (Track llt : _lastLocationTracks) { 915 Train middleTrain3 = getTrainForCar(testCar, mlt, llt, middleTrain2, 916 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))); 917 if (middleTrain3 == null) { 918 continue; 919 } 920 log.debug("Found 4 train route, setting car destination ({}, {})", nlt.getLocation().getName(), 921 nlt.getName()); 922 foundRoute = true; 923 // show the car's route by building an ordered list of 924 // trains and tracks 925 List<Train> trains = new ArrayList<>( 926 Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2, 927 middleTrain3, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)))); 928 List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt, llt, car.getFinalDestinationTrack())); 929 showRoute(car, trains, tracks); 930 if (finshSettingRouteFor(car, nlt)) { 931 return true; // done 4 train routing 932 } 933 break otherloop; // there was an issue with the first 934 // stop in the route 935 } 936 } 937 } 938 return foundRoute; 939 } 940 941 private boolean routeUsing5Trains(Car car) { 942 addLine(Bundle.getMessage("RouterNTrains", "5", car.getFinalDestinationName(), 943 car.getFinalDestinationTrackName())); 944 Car testCar = clone(car); // reload 945 boolean foundRoute = false; 946 for (Track nlt : _nextLocationTracks) { 947 otherloop: for (Track mlt1 : _next2ndLocationTracks) { 948 Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1, 949 _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null); 950 if (middleTrain2 == null) { 951 continue; 952 } 953 for (Track mlt2 : _otherLocationTracks) { 954 if (_next2ndLocationTracks.contains(mlt2)) { 955 continue; 956 } 957 Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null); 958 if (middleTrain3 == null) { 959 continue; 960 } 961 // build a list of tracks that are reachable from the 2nd 962 // interchange 963 if (!_next3rdLocationTracks.contains(mlt2)) { 964 _next3rdLocationTracks.add(mlt2); 965 if (_addtoReport) { 966 addLine(Bundle.getMessage("RouterNextHop", mlt2.getTrackTypeName(), 967 mlt2.getLocation().getName(), 968 mlt2.getName(), car, mlt1.getLocation().getName(), mlt1.getName(), 969 middleTrain3.getName())); 970 } 971 } 972 for (Track llt : _lastLocationTracks) { 973 Train middleTrain4 = getTrainForCar(testCar, mlt2, llt, middleTrain3, 974 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))); 975 if (middleTrain4 == null) { 976 continue; 977 } 978 log.debug("Found 5 train route, setting car destination ({}, {})", 979 nlt.getLocation().getName(), 980 nlt.getName()); 981 foundRoute = true; 982 // show the car's route by building an ordered list 983 // of trains and tracks 984 List<Train> trains = new ArrayList<>(Arrays.asList( 985 _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2, middleTrain3, 986 middleTrain4, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)))); 987 List<Track> tracks = 988 new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, llt, car.getFinalDestinationTrack())); 989 showRoute(car, trains, tracks); 990 if (finshSettingRouteFor(car, nlt)) { 991 return true; // done 5 train routing 992 } 993 break otherloop; // there was an issue with the 994 // first stop in the route 995 } 996 } 997 } 998 } 999 return foundRoute; 1000 } 1001 1002 private boolean routeUsing6Trains(Car car) { 1003 addLine(Bundle.getMessage("RouterNTrains", "6", car.getFinalDestinationName(), 1004 car.getFinalDestinationTrackName())); 1005 Car testCar = clone(car); // reload 1006 boolean foundRoute = false; 1007 for (Track nlt : _nextLocationTracks) { 1008 otherloop: for (Track mlt1 : _next2ndLocationTracks) { 1009 Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1, 1010 _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null); 1011 if (middleTrain2 == null) { 1012 continue; 1013 } 1014 for (Track mlt2 : _next3rdLocationTracks) { 1015 Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null); 1016 if (middleTrain3 == null) { 1017 continue; 1018 } 1019 for (Track mlt3 : _otherLocationTracks) { 1020 if (_next2ndLocationTracks.contains(mlt3) || _next3rdLocationTracks.contains(mlt3)) { 1021 continue; 1022 } 1023 Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null); 1024 if (middleTrain4 == null) { 1025 continue; 1026 } 1027 if (!_next4thLocationTracks.contains(mlt3)) { 1028 _next4thLocationTracks.add(mlt3); 1029 if (_addtoReport) { 1030 addLine(Bundle.getMessage("RouterNextHop", mlt3.getTrackTypeName(), 1031 mlt3.getLocation().getName(), mlt3.getName(), car, mlt2.getLocation().getName(), 1032 mlt2.getName(), middleTrain4.getName())); 1033 } 1034 } 1035 for (Track llt : _lastLocationTracks) { 1036 Train middleTrain5 = getTrainForCar(testCar, mlt3, llt, middleTrain4, 1037 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))); 1038 if (middleTrain5 == null) { 1039 continue; 1040 } 1041 log.debug("Found 6 train route, setting car destination ({}, {})", 1042 nlt.getLocation().getName(), nlt.getName()); 1043 foundRoute = true; 1044 // show the car's route by building an ordered 1045 // list of trains and tracks 1046 List<Train> trains = new ArrayList<>( 1047 Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), 1048 middleTrain2, middleTrain3, middleTrain4, middleTrain5, 1049 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)))); 1050 List<Track> tracks = new ArrayList<>( 1051 Arrays.asList(nlt, mlt1, mlt2, mlt3, llt, car.getFinalDestinationTrack())); 1052 showRoute(car, trains, tracks); 1053 // only set car's destination if specified train 1054 // can service car 1055 if (finshSettingRouteFor(car, nlt)) { 1056 return true; // done 6 train routing 1057 } 1058 break otherloop; // there was an issue with the 1059 // first stop in the route 1060 } 1061 } 1062 } 1063 } 1064 } 1065 return foundRoute; 1066 } 1067 1068 private boolean routeUsing7Trains(Car car) { 1069 addLine(Bundle.getMessage("RouterNTrains", "7", car.getFinalDestinationName(), 1070 car.getFinalDestinationTrackName())); 1071 Car testCar = clone(car); // reload 1072 boolean foundRoute = false; 1073 for (Track nlt : _nextLocationTracks) { 1074 otherloop: for (Track mlt1 : _next2ndLocationTracks) { 1075 Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1, 1076 _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null); 1077 if (middleTrain2 == null) { 1078 continue; 1079 } 1080 for (Track mlt2 : _next3rdLocationTracks) { 1081 Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null); 1082 if (middleTrain3 == null) { 1083 continue; 1084 } 1085 for (Track mlt3 : _next4thLocationTracks) { 1086 Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null); 1087 if (middleTrain4 == null) { 1088 continue; 1089 } 1090 for (Track mlt4 : _otherLocationTracks) { 1091 if (_next2ndLocationTracks.contains(mlt4) || 1092 _next3rdLocationTracks.contains(mlt4) || 1093 _next4thLocationTracks.contains(mlt4)) { 1094 continue; 1095 } 1096 Train middleTrain5 = getTrainForCar(testCar, mlt3, mlt4, middleTrain4, null); 1097 if (middleTrain5 == null) { 1098 continue; 1099 } 1100 for (Track llt : _lastLocationTracks) { 1101 Train middleTrain6 = getTrainForCar(testCar, mlt4, llt, middleTrain5, 1102 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))); 1103 if (middleTrain6 == null) { 1104 continue; 1105 } 1106 log.debug("Found 7 train route, setting car destination ({}, {})", 1107 nlt.getLocation().getName(), nlt.getName()); 1108 foundRoute = true; 1109 // show the car's route by building an ordered 1110 // list of trains and tracks 1111 List<Train> trains = new ArrayList<>( 1112 Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), 1113 middleTrain2, middleTrain3, middleTrain4, middleTrain5, middleTrain6, 1114 _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)))); 1115 List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, mlt3, mlt4, llt, 1116 car.getFinalDestinationTrack())); 1117 showRoute(car, trains, tracks); 1118 // only set car's destination if specified train 1119 // can service car 1120 if (finshSettingRouteFor(car, nlt)) { 1121 return true; // done 7 train routing 1122 } 1123 break otherloop; // there was an issue with the 1124 // first stop in the route 1125 } 1126 } 1127 } 1128 } 1129 } 1130 } 1131 return foundRoute; 1132 } 1133 1134 /** 1135 * This method returns a train that is able to move the test car between the 1136 * fromTrack and the toTrack. The default for an interchange track is to not 1137 * allow the same train to spot and pull a car. 1138 * 1139 * @param testCar test car 1140 * @param fromTrack departure track 1141 * @param toTrack arrival track 1142 * @param fromTrain train servicing fromTrack (previous drop to fromTrack) 1143 * @param toTrain train servicing toTrack (pulls from the toTrack) 1144 * @return null if no train found, else a train able to move test car 1145 * between fromTrack and toTrack. 1146 */ 1147 private Train getTrainForCar(Car testCar, Track fromTrack, Track toTrack, Train fromTrain, Train toTrain) { 1148 testCar.setTrack(fromTrack); // car to this location and track 1149 testCar.setDestinationTrack(toTrack); // car to this destination & track 1150 List<Train> excludeTrains = new ArrayList<>(); 1151 if (fromTrack.isInterchange() && fromTrack.getPickupOption().equals(Track.ANY)) { 1152 excludeTrains.add(fromTrain); 1153 } 1154 if (toTrack.isInterchange() && toTrack.getPickupOption().equals(Track.ANY)) { 1155 excludeTrains.add(toTrain); 1156 } 1157 // does a train service these two locations? 1158 String key = fromTrack.getId() + toTrack.getId(); 1159 Train train = _listTrains.get(key); 1160 if (train == null) { 1161 train = trainManager.getTrainForCar(testCar, excludeTrains, null, true); 1162 if (train != null) { 1163 _listTrains.put(key, train); 1164 } else { 1165 _listTrains.put(key, new Train("null", "null")); 1166 } 1167 } else if (train.getId().equals("null")) { 1168 return null; 1169 } 1170 return train; 1171 1172 } 1173 1174 private void showRoute(Car car, List<Train> trains, List<Track> tracks) { 1175 StringBuffer buf = new StringBuffer( 1176 Bundle.getMessage("RouterRouteForCar", car.toString(), car.getLocationName(), car.getTrackName())); 1177 StringBuffer bufRp = new StringBuffer( 1178 Bundle.getMessage("RouterRoutePath", car.getLocationName(), car.getTrackName())); 1179 for (Track track : tracks) { 1180 if (_addtoReport) { 1181 buf.append(Bundle.getMessage("RouterRouteTrain", trains.get(tracks.indexOf(track)).getName())); 1182 } 1183 bufRp.append(Bundle.getMessage("RouterRoutePathTrain", trains.get(tracks.indexOf(track)).getName())); 1184 if (track != null) { 1185 buf.append(Bundle.getMessage("RouterRouteTrack", track.getLocation().getName(), track.getName())); 1186 bufRp.append( 1187 Bundle.getMessage("RouterRoutePathTrack", track.getLocation().getName(), track.getName())); 1188 } else { 1189 buf.append(Bundle.getMessage("RouterRouteTrack", car.getFinalDestinationName(), 1190 car.getFinalDestinationTrackName())); 1191 bufRp.append(Bundle.getMessage("RouterRoutePathTrack", car.getFinalDestinationName(), 1192 car.getFinalDestinationTrackName())); 1193 } 1194 } 1195 car.setRoutePath(bufRp.toString()); 1196 addLine(buf.toString()); 1197 } 1198 1199 /** 1200 * @param car The car to which the destination (track) is going to be 1201 * applied. Will set car's destination if specified train can 1202 * service car 1203 * @param track The destination track for car 1204 * @return false if there's an issue with the destination track length or 1205 * wrong track into staging, otherwise true. 1206 */ 1207 private boolean finshSettingRouteFor(Car car, Track track) { 1208 // only set car's destination if specified train can service car 1209 Car ts2 = clone(car); 1210 ts2.setDestinationTrack(track); 1211 String specified = canSpecifiedTrainService(ts2); 1212 if (specified.equals(NO)) { 1213 addLine(Bundle.getMessage("TrainDoesNotServiceCar", 1214 _train.getName(), car.toString(), track.getLocation().getName(), track.getName())); 1215 _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()}); 1216 return false; 1217 } else if (specified.equals(NOT_NOW)) { 1218 addLine(Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(), 1219 track.getLocation().getName(), track.getName(), _train.getServiceStatus())); 1220 return false; // the issue is route moves or train length 1221 } 1222 // check to see if track is staging 1223 if (track.isStaging() && 1224 _train != null && 1225 _train.getTerminationTrack() != null && 1226 _train.getTerminationTrack() != track) { 1227 addLine(Bundle.getMessage("RouterTrainIntoStaging", 1228 _train.getName(), _train.getTerminationTrack().getLocation().getName(), 1229 _train.getTerminationTrack().getName())); 1230 return false; // wrong track into staging 1231 } 1232 _status = car.setDestination(track.getLocation(), track); 1233 if (!_status.equals(Track.OKAY)) { 1234 addLine(Bundle.getMessage("RouterCanNotDeliverCar", car.toString(), 1235 track.getLocation().getName(), track.getName(), _status, track.getTrackTypeName())); 1236 if (_status.startsWith(Track.LENGTH) && !redirectToAlternate(car, track)) { 1237 return false; 1238 } 1239 } 1240 return true; 1241 } 1242 1243 /** 1244 * Used when the 1st hop interchanges and yards are full. Will attempt to 1245 * use a spur's alternate track when pulling a car from the spur. This will 1246 * create a local move. Code checks to see if local move by the train being 1247 * used is allowed. Will only use the alternate track if all possible 1st 1248 * hop tracks were tested. 1249 * 1250 * @param car the car being redirected 1251 * @return true if car's destination was set to alternate track 1252 */ 1253 private boolean redirectToAlternate(Car car, Track track) { 1254 if (car.getTrack().isSpur() && 1255 car.getTrack().getAlternateTrack() != null && 1256 _nextLocationTracks.indexOf(track) == _nextLocationTracks.size() - 1) { 1257 // try redirecting car to the alternate track 1258 Car ts = clone(car); 1259 ts.setDestinationTrack(car.getTrack().getAlternateTrack()); 1260 String specified = canSpecifiedTrainService(ts); 1261 if (specified.equals(YES)) { 1262 _status = car.setDestination(car.getTrack().getAlternateTrack().getLocation(), 1263 car.getTrack().getAlternateTrack()); 1264 if (_status.equals(Track.OKAY)) { 1265 addLine(Bundle.getMessage("RouterSendCarToAlternative", 1266 car.toString(), car.getTrack().getAlternateTrack().getName(), 1267 car.getTrack().getAlternateTrack().getLocation().getName())); 1268 return true; 1269 } 1270 } 1271 } 1272 return false; 1273 } 1274 1275 // sets clone car destination to final destination and track 1276 private Car clone(Car car) { 1277 Car clone = car.copy(); 1278 // modify clone car length if car is part of kernel 1279 if (car.getKernel() != null) { 1280 clone.setLength(Integer.toString(car.getKernel().getTotalLength() - RollingStock.COUPLERS)); 1281 } 1282 clone.setTrack(car.getTrack()); 1283 clone.setFinalDestination(car.getFinalDestination()); 1284 // don't set the clone's final destination track, that will record the 1285 // car as being inbound 1286 // next two items is where the clone is different 1287 clone.setDestination(car.getFinalDestination()); 1288 // note that final destination track can be null 1289 clone.setDestinationTrack(car.getFinalDestinationTrack()); 1290 return clone; 1291 } 1292 1293 /* 1294 * Creates two sets of tracks when routing. 1st set (_nextLocationTracks) is 1295 * one hop away from car's current location. 2nd set is all other tracks 1296 * (_otherLocationTracks) that aren't one hop away from car's current 1297 * location or destination. Also creates the list of trains used to service 1298 * _nextLocationTracks. 1299 */ 1300 private void loadTracksAndTrains(Car car, Car testCar, List<Track> tracks) { 1301 for (Track track : tracks) { 1302 if (track == car.getTrack()) { 1303 continue; // don't use car's current track 1304 } 1305 // note that last could equal next if this routine was used for two 1306 // train routing 1307 if (_lastLocationTracks.contains(track)) { 1308 continue; 1309 } 1310 String status = track.isRollingStockAccepted(testCar); 1311 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1312 continue; // track doesn't accept this car 1313 } 1314 // test to see if there's a train that can deliver the car to this 1315 // destination 1316 testCar.setDestinationTrack(track); 1317 Train train = null; 1318 String specified = canSpecifiedTrainService(testCar); 1319 if (specified.equals(YES) || specified.equals(NOT_NOW)) { 1320 train = _train; 1321 } else { 1322 train = trainManager.getTrainForCar(testCar, null); 1323 } 1324 // Can specified train carry this car out of staging? 1325 if (car.getTrack().isStaging() && specified.equals(NO)) { 1326 train = null; 1327 } 1328 // is the option carry all cars with a final destination enabled? 1329 if (train != null && 1330 _train != null && 1331 _train != train && 1332 _train.isServiceAllCarsWithFinalDestinationsEnabled() && 1333 !specified.equals(YES)) { 1334 addLine(Bundle.getMessage("RouterOptionToCarry", _train.getName(), 1335 train.getName(), car.toString(), track.getLocation().getName(), track.getName())); 1336 train = null; 1337 } 1338 if (train != null) { 1339 _nextLocationTracks.add(track); 1340 _nextLocationTrains.add(train); 1341 } else { 1342 _otherLocationTracks.add(track); 1343 } 1344 } 1345 } 1346 1347 private static final String NO = "no"; // NOI18N 1348 private static final String YES = "yes"; // NOI18N 1349 private static final String NOT_NOW = "not now"; // NOI18N 1350 private static final String NO_SPECIFIED_TRAIN = "no specified train"; // NOI18N 1351 1352 private String canSpecifiedTrainService(Car car) { 1353 if (_train == null) { 1354 return NO_SPECIFIED_TRAIN; 1355 } 1356 if (_train.isServiceable(car)) { 1357 return YES; 1358 } // is the reason this train can't service route moves or train length? 1359 else if (!_train.getServiceStatus().equals(Train.NONE)) { 1360 return NOT_NOW; // the issue is route moves or train length 1361 } 1362 return NO; 1363 } 1364 1365 private static final Logger log = LoggerFactory.getLogger(Router.class); 1366 1367 // all router build report messages are at level seven 1368 protected void addLine(String string) { 1369 addLine(_buildReport, SEVEN, string); 1370 } 1371 1372}