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