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