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